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

Symbols Created in Stash at Runtime Not Available in PseudoStash in 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::;
$ ./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";

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

outer.raku

#!/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::;
$ ./outer.raku 
bar
PseudoStash.new(($?PACKAGE => (Foobar), $_ => (Any), $foo => foo, &outer => &outer, ::?PACKAGE => (Foobar)))
&outer => &outer
$bar => bar
$foo => foo

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

什么时候答案是 nanswer?

虽然我很高兴我写了这篇文章,但我对它不满意作为对我得出的结论的回答是你问题的核心,即为什么没有办法让EVAL违反编译时词法符号被冻结的原则

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

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

我很想将这个答案复制到要点中,link 从对你问题的评论中复制到它,然后删除这个答案。

或者,如果您同意我的回答中存在这个漏洞,也许您会不接受它,然后我可以悬赏它以尝试从 jnthn 那里得到答案,and/or 鼓励别人回答?

快速修复

  1. 添加包限定符:

    package Foo {
      our sub outer {
        EVAL 'say $Foo::bar'  # Insert `Foo::`
      }
    }
    
    $Foo::bar = 'bar';
    Foo::outer; # bar
    

或:

  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
    

    (阅读 的开头部分(直到“so: our $foo = 42; Is doing this: (my $foo := $?PACKAGE.WHO<$foo>) = 42;”)到一个相当不相关的 SO 问题可能会有所帮助。)

您问题的初步答案

why are symbols created at runtime not available to EVAL?

他们可用。

但是:

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

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

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

因此,例如,给定 $foo::bar = 42; 语句在包 foo:

中的 运行 时间声明符号 $bar
  • 一个 $bar 符号将被添加到与包 foo;

    关联的包存储 (Stash)
  • foo 及其关联的存储,如果尚不存在,将依次创建,同时将符号 foo 添加到现有包stash 对应于包含 $foo::bar = 42; 语句的包。

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


why are symbols created at runtime not available to PseudoStashs?

内置的PseudoStash类型被language/compiler用来在编译时存储词法符号].


讨论 lexicalpackage scopes/symbols/stashes 之间的这种区别的基本原理(以及诸如 our 声明符对两者的使用;以及可以创建 词法包 的事实,请参阅 .

的答案

所有关于 藏品

有两种内置存储类型:

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

  • Stashs 用于 package stashes language/compiler 添加 package 符号(在编译时或 运行 时)在编译时或 运行 时打包存储。用户代码可以添加、删除或修改包存储和包符号。

词法 隐藏

这些由 language/compiler 管理并在编译结束时冻结。


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

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

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


您可以 引用 词汇存储及其中的符号,方法是使用各种 "pseudo-packages"[3] 例如 MYOUTERCOREUNIT.


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

my $foo = 42;
$MY::foo = 99;    # Assign works:
say $foo;         # 99
$MY::foo := 100;  # Binding too:
say $foo;         # 100

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

$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)

EVAL 坚持认为 不合格的 符号(参考中没有 ::,所以像普通 $bar 这样的参考)是 词汇个符号。 (有关基本原理,请参阅开头附近的 SO I linked。)

包裹藏品

包存储由 language/compiler 根据用户代码根据需要创建。


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

This... refers to the package stash associated with...
OUR:: the scope in which the OUR appears
GLOBAL:: the interpreter
PROCESS:: the process in which the interpreter is running

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

our $foo = 42;

这会向对应于最内层封闭词法范围的 lexical 存储和 package 添加一个 $foo 符号stash对应范围:

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)

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

OUR::<$foo>:delete;
say $OUR::foo; # (Any)
$OUR::foo = 99;
say $OUR::foo; # 99

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

say $foo;      # 42
say $MY::foo;  # 42

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

package Foo { my $bar; our $baz }

如果在 package 声明符之前没有范围声明符(例如 myour),则假定为 our。因此上面的代码将:

  • 创建一个新的Foo交易品种;

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

  • 创建一个新的 package 存储,并将其与 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)

MY lexical 存储中 Foo 符号的值与 OUR 存储。该值绑定到另一个 package 存储,通过 Foo.WHO aka Foo::.

访问

所以 MY::Foo::.keysOUR::Foo::.keys 列出了相同的符号,即只是 $baz,它在 Foo 包的存储中。

不要看到$bar,因为它在词法存储中,它对应于与Foo 包,但仍然是一个独特的藏品。更一般地说,你不能外部看到$bar,因为一个关键的Raku设计元素是用户和编译器由于其词法性质,可以依赖于 100% 封装的纯词法范围符号。


虽然您甚至无法看到任何词法符号,但您不仅可以看到[=235] =] 修改 任何 符号从任何你可以访问与其封闭包对应的符号的地方:

package Foo { our sub qux { say $Foo::baz } }
$Foo::baz = 99;
Foo::qux; # 99

$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

EVAL 将愉快地使用在 运行 时添加的符号,前提是对它们的引用是适当限定的:

package Foo { our sub bar { say EVAL '$OUR::baz' } }
$Foo::baz = 99;
Foo::bar; # 99

脚注

[1] 它们之所以被称为“藏品”,是因为它们是 s符号 table hashes.

[2] 抽象概念“符号”实现为 Pairs 存储在 stashes.

[3] 术语“伪包”可能有点不幸,因为其中有几个是 [=28 的别名=]s 而不是 PseudoStashs.

[4] 对于像 Foo::Bar 这样的引用,它不以印记开头但包含 ::,您需要确保遵守 Raku 的规则来解决此类引用。我仍在弄清楚这些到底是什么,并打算在我确定后更新这个答案,但我决定 post 同时保持这个答案。