在运行时在 Stash 中创建的符号在 Raku 的 PseudoStash 中不可用

Jus*_*Guy 6 scope eval scoping raku

这个问题始于我试图弄清楚为什么在运行时创建的符号对EVAL.

外-EVAL.raku

#!/usr/bin/env raku

use MONKEY-SEE-NO-EVAL;

package Foobar {
  our $foo = 'foo';

  our sub eval {
    say OUTER::;
    EVAL "say $bar";
  }
}

Foobar::<$bar> = 'bar';
say $Foobar::bar;

Foobar::eval;

.say for Foobar::;
Run Code Online (Sandbox Code Playgroud)
$ ./outer-EVAL.raku 
===SORRY!=== Error while compiling /development/raku/VTS-Template.raku/scratchpad/./outer-EVAL.raku
Variable '$bar' is not declared
at /development/raku/VTS-Template.raku/scratchpad/./outer-EVAL.raku:10
------>     EVAL "say ?$bar";
Run Code Online (Sandbox Code Playgroud)

认为这与以这种方式创建的符号在PseudoStashs 中不可用有关。但我可能是错的。

外乐

#!/usr/bin/env raku

package Foobar {
  our $foo = 'foo';

  our sub outer {
    say OUTER::;
  }
}

Foobar::<$bar> = 'bar';
say $Foobar::bar;

Foobar::outer;

.say for Foobar::;
Run Code Online (Sandbox Code Playgroud)
$ ./outer.raku 
bar
PseudoStash.new(($?PACKAGE => (Foobar), $_ => (Any), $foo => foo, &outer => &outer, ::?PACKAGE => (Foobar)))
&outer => &outer
$bar => bar
$foo => foo
Run Code Online (Sandbox Code Playgroud)

如您所见,$Foobar::bar在 中Foobar:: Stash,但不在 中OUTER:: PseudoStash。所以,我的问题是双重的:为什么在运行时创建的EVAL符号对PseudoStashs不可用,为什么在运行时创建的符号对s不可用?

rai*_*iph 8

什么时候回答是 nanswer?

虽然我很高兴我写了这个,但我对它不满意,因为我得出的结论是你问题的核心,即为什么没有办法EVAL违反词汇符号在期间冻结的原则汇编。

Aiui 的基本原理归结为 A) 避免危险的安全漏洞,以及 B) 在某些MONKEYpragma下允许违规被认为是不值得的。

但是,这是否准确,并讨论它,以及 Rakudo 插件可能导致违反词汇原则的任何可能性,都远远超出了我的薪水。

我很想将此答案复制到要点中,从对您的问题的评论中链接到它,然后删除此答案。

或者,如果您同意我的答案中有这个大漏洞,也许您会善意地不接受它,然后我可以悬赏以尝试从 jnthn 那里得到答案,和/或鼓励其他人回答?

快速修复

  1. 添加包限定符:

    package Foo {
      our sub outer {
        EVAL 'say $Foo::bar'  # Insert `Foo::`
      }
    }
    
    $Foo::bar = 'bar';
    Foo::outer; # bar
    
    Run Code Online (Sandbox Code Playgroud)

或者:

  1. 使用词法(编译时)符号:

    package Foo {
      our $bar;          # Create *two* symbols *bound together*
      our sub outer {
        EVAL 'say $bar'  # Use *lexical* symbol from *lexical* stash
      }
    }
    $Foo::bar = 'bar';   # Use *package* symbol from *package* stash
    Foo::outer;          # bar
    
    Run Code Online (Sandbox Code Playgroud)

    (阅读jnthn对一个相当不相关的 SO 问题的回答的开头部分(直到“所以:our $foo = 42;正在这样做:(my $foo := $?PACKAGE.WHO<$foo>) = 42;”)可能会有所帮助。)

对您问题的初步回答

为什么在运行时创建的符号不可用于EVAL

他们可用的。

但:

  • 在运行时创建的符号只能在一些现有的或新的符号表哈希(又名“stash” [1])中创建(哪个 stash 必须有一些命名它的符号,而这个符号只能在一些现有的或新的stash,以此类推);

  • 此类符号的存储必须是包存储(使用内置Stash类型),而不是词法存储(使用PseudoStash类型)。

  • 代码中对符号的任何引用都必须将封闭包命名为该引用的一部分。

因此,例如,给定在运行时在包中$foo::bar = 42;声明符号的语句:$barfoo

  • 一个$bar符号将被添加到与包Stash关联的包 stash( ) 中foo

  • 如果包foo不存在,则将创建该包及其关联的存储,同时将符号foo添加到与包含该$foo::bar = 42;语句的包相对应的现有包存储中。

然后,要引用$bar您必须编写的符号$foo::bar(或使用其他形式的包限定引用之一,例如foo::<$bar>)。不允许将其称为 just $bar


为什么在运行时创建的符号对PseudoStashs不可用?

PseudoStash语言/编译器使用内置类型在编译时存储词法符号。


有关词法范围/符号/存储之间这种区别的基本原理的讨论(以及our声明符对两者的使用等并发症;以及可以创建词法包的事实),请参阅之间有什么区别?my classour class.

关于藏匿处

有两种内置的存储类型:

  • PseudoStashs 用于词法隐藏语言/编译器在编译时将(静态)词法符号[2] 添加到词法隐藏中。用户代码只能使用作为其操作一部分的语言构造来间接修改词法存储(例如my $fooour $bar两者都将词法符号添加到词法存储中)。

  • Stashs为用于储物箱的语言/编译器添加符号(在编译时或运行时)到包装期间编译时或运行时藏匿。用户代码可以添加、删除或修改包存储和包符号。

词汇隐藏

这些由语言/编译器管理并在编译结束时冻结。


您可以添加全新的词法存储,并添加到现有的词法存储中,但只能通过在语言的精确控制下使用语言结构,例如:

  • 一个{ ... }词法范围。这将导致编译器创建一个与作用域相对应的新词法存储。

  • package Foo {}, use Foo;,my \Foo = 42;等。作为编译这些语句的一部分,语言/编译器将向Foo与包含此类语句的最内层词法范围相对应的词法存储添加一个符号。(对于前两个,它还将创建一个新的存储并将其与Foo符号的值相关联。可以通过Foo.WHO或访问该存储Foo::。)


可以参考词法储物箱,以及其中的符号,通过使用各种“伪包” [3]MYOUTERCORE,和UNIT


您可以使用与词法存储关联的伪包分配绑定到这些词法存储中的现有符号:

my $foo = 42;
$MY::foo = 99;    # Assign works:
say $foo;         # 99
$MY::foo := 100;  # Binding too:
say $foo;         # 100
Run Code Online (Sandbox Code Playgroud)

但这是您唯一可以做的修改。您不能以其他方式修改这些隐藏或它们包含的符号:

$MY::$foo = 99;          # Cannot modify ...
BEGIN $MY::foo = 99;     # Cannot modify ...
my $bar;
MY::<$bar>:delete;       # Can not remove values from a PseudoStash
BEGIN MY::<$bar>:delete; # (Silently fails)
Run Code Online (Sandbox Code Playgroud)

EVAL坚持认为不合格的符号(没有::在引用中,所以像 plain 这样的引用$bar)是词法符号。(有关基本原理,请参阅我在开头附近链接的 SO。)

包裹藏匿处

包存储由语言/编译器根据用户代码根据需要创建。


与词法存储一样,您可以通过一些“伪包”名称来引用包存储。

这个... 指的是与...相关联的藏匿处
OUR:: 在其中的范围OUR出现
GLOBAL:: 口译员
PROCESS:: 解释器运行的进程

由于语言结构(例如声明)的隐含含义,可以将符号添加到包存储中our

our $foo = 42;
Run Code Online (Sandbox Code Playgroud)

这会向与最内部封闭词法范围对应$foo词法存储和与该范围对应的存储添加一个符号:

say $foo;      # 42  (Accesses `$foo` symbol in enclosing *lexical* stash)
say $MY::foo;  # 42  (Same)
say $OUR::foo; # 42  (Accesses `$foo` symbol in enclosing *package* stash)
Run Code Online (Sandbox Code Playgroud)

与词法存储不同,包存储是可修改的。继续上面的代码:

OUR::<$foo>:delete;
say $OUR::foo; # (Any)
$OUR::foo = 99;
say $OUR::foo; # 99
Run Code Online (Sandbox Code Playgroud)

所有这些都使词汇存储保持不变:

say $foo;      # 42
say $MY::foo;  # 42
Run Code Online (Sandbox Code Playgroud)

由于用户代码的隐含含义,也可以添加包隐藏:

package Foo { my $bar; our $baz }
Run Code Online (Sandbox Code Playgroud)

假定声明符之前没有范围声明符(例如myour)。因此上面的代码将:packageour

  • 创建一个新Foo符号;

  • 安装该Foo符号的两个副本,一个在对应于最内部封闭词法范围(可通过 访问)的词法存储中MY::,另一个在对应于该范围的存储中(可通过 访问OUR::);

  • 创建一个新的存储,并将其与Foo类型对象相关联,可通过编写Foo::或访问Foo.WHO

因此,尽管最初有任何惊喜,但现在这有望变得有意义:

package Foo { my $bar; our $baz }
say  MY::Foo;        # (Foo)
say OUR::Foo;        # (Foo)
say  MY::Foo::.keys; # ($baz)
say OUR::Foo::.keys; # ($baz)
Run Code Online (Sandbox Code Playgroud)

词法存储中的Foo符号值与存储中的值完全相同。该值绑定到另一个存储,通过aka访问。MY OUR Foo.WHOFoo::

因此,MY::Foo::.keysOUR::Foo::.keys列出相同的符号,即 just $baz,它位于Foo包的存储中。

没有看到,$bar因为它在词法存储中,它对应于与Foo包相同的周围范围,但仍然是一个不同的存储。更一般地,你不能看到$bar外面是支撑代码,因为一个关键乐的设计元素是用户和编译器可以依靠纯词法范围符号为100%密封,由于其性质的词汇。


虽然您甚至无法从其词法范围之外看到任何词法符号,但您不仅可以从任何可以访问与其封闭包对应的符号的地方看到而且可以修改任何符号:

package Foo { our sub qux { say $Foo::baz } }
$Foo::baz = 99;
Foo::qux; # 99
Run Code Online (Sandbox Code Playgroud)

$Foo::Bar::Baz::qux = 99;如有必要,类似的行将自动激活任何不存在的包存储,然后可以使用包存储引用(例如伪包OUR[4])查看这些信息:

$Foo::Bar::Baz::qux = 99;
say OUR::Foo::.WHAT;           # (Stash)
say OUR::Foo::Bar::.WHAT;      # (Stash)
say OUR::Foo::Bar::Baz::.WHAT; # (Stash)
say $Foo::Bar::Baz::qux;       # 99
Run Code Online (Sandbox Code Playgroud)

EVAL 如果对它们的引用适当限定,将很乐意使用在运行时添加的符号:

package Foo { our sub bar { say EVAL '$OUR::baz' } }
$Foo::baz = 99;
Foo::bar; # 99
Run Code Online (Sandbox Code Playgroud)

脚注

[1]他们被称为“储物箱”,因为他们是小号ymbol牛逼能^ hES。

[2]抽象概念“符号”被实现为Pair存储在 stash 中的 s。

[3]术语“伪包”可能有点不幸,因为其中一些是Stashs 而不是PseudoStashs 的别名。

[4]对于Foo::Bar不以 sigil 开头但包含 的引用之类的引用::,您需要确保遵守 Raku 解决此类引用的规则。我仍在弄清楚这些到底是什么,并打算在确定后更新此答案,但我决定在此期间按原样发布此答案。