【问题标题】:Symbols Created in Stash at Runtime Not Available in PseudoStash in Raku运行时在 Stash 中创建的符号在 Raku 的 PseudoStash 中不可用
【发布时间】:2021-08-22 23:13:15
【问题描述】:

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

outer-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";

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

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 不可用,为什么在运行时创建的符号对PseudoStashs 不可用?

【问题讨论】:

    标签: scope eval raku scoping


    【解决方案1】:

    什么时候回答是nanswer?

    虽然我很高兴我写了这篇文章,但我对它不满意因为我的结论是你问题的核心,即为什么 em> 没有办法让EVAL 违反编译过程中词法符号被冻结的原则。

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

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

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

    或者,如果您同意我的回答中存在这个巨大的漏洞,也许您会好心不接受它,然后我可以悬赏它以尝试从 jnthn 获得答案,和/或鼓励其他人回答?

    快速修复

    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
      

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

    对您的问题的初步回答

    为什么EVAL 无法在运行时创建符号?

    他们可用的。

    但是:

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

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

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

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

    • $bar 符号将添加到与包 foo 关联的包存储 (Stash);

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

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


    为什么PseudoStashs 无法在运行时创建符号?

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


    讨论 lexicalpackage 作用域/符号/stashes 之间这种区别的基本原理(以及诸如our 声明符对两者的使用的复杂性;以及事实上可以创建词法包),请参阅What is the difference between my class and our class? 的答案。

    关于隐藏

    有两种内置的存储类型:

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

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

    词法隐藏

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


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

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

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


    您可以通过使用各种 "pseudo-packages"[3](例如 @987654368)引用词法存储以及其中的符号@、OUTERCOREUNIT


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

    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 坚持 unqualified 符号(引用中没有 ::,所以像普通的 $bar 这样的引用)是 词法 符号。 (有关基本原理,请参阅我在开头附近链接的 SO。)

    包裹藏匿

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


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

    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;
    

    这会将$foo 符号添加到对应于最里面的封闭词法范围的lexical 存储,以及对应于范围的 存储:

    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
    

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

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

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

    package Foo { my $bar; our $baz }
    

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

    • 创建一个新的Foo 符号;

    • 安装该Foo 符号的两份副本,一份在lexical 存储区对应于最里面的封闭词法范围(可通过MY:: 访问),另一份在包 对应范围的 stash(可通过OUR:: 访问);

    • 创建一个新的 stash,并将其与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 stash 中的Foo 符号的值与OUR package stash 中的值完全相同。该值绑定到另一个 package stash,通过 Foo.WHO aka Foo:: 访问。

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

    没有看到$bar,因为它在lexical stash 中,它对应于与Foo 包相同的周围范围,但仍然是一个不同的stash .更一般地说,您无法外部 看到 $bar 括号内的代码,因为关键的 Raku 设计元素是用户和编译器可以依赖纯词法范围的符号由于其词汇性质,100% 被封装。


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

    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] 它们被称为“stashes”,因为它们是 s 符号 table h es。

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

    [3] 术语“伪包”可能有点令人遗憾,因为其中有几个是Stashs 的别名,而不是PseudoStashs。

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

    【讨论】:

    • 感谢您的详尽回答,@raiph!我猜不可能 EVAL 非限定符号。我想知道 NQP 是否有可能?
    • @JustThisGuy 我已经完全重写了我的答案。请考虑出于一开始给出的原因不接受这个答案,除非你真的,真的对它完全满意。如果没有其他结果并且您不想让问题悬而未决,您可以随时重新接受它。我希望看到 jnthn 至少简要讨论一下为什么没有 MONKEY pragma 让 EVAL 更加邪恶。
    • @JustThisGuy (希望大多数情况下)把这个(n)答案放在床上后,我去阅读 reddit,发现 gfldex 写了this,这表明拥有足够 MOP 印章的人可以相当轻松弯曲包声明符的含义(如 jnthn 的actor)。我不知道这条路是否可以解决你的情况,或者疯狂,或者两者兼而有之,或者两者都没有,但 gfldex 的结论是“我曾经认为 Raku 不如 Perl 动态。看起来我必须重新考虑。”,以及他们的 Raku魔法远远领先于我的米老鼠幻想曲!
    • 再次感谢!我暂时不接受。明天我应该有时间重新阅读您的答案,然后我会查看该 reddit 帖子。关于 Raku 不那么动态的评论很有趣,因为尝试将一些 Perl 代码移植到 Raku 是让我走上这条道路的原因。我也很想知道是否有任何想法让EVAL 变得更加邪恶。 :)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-08-09
    • 1970-01-01
    • 2020-12-07
    • 2015-10-11
    • 1970-01-01
    • 1970-01-01
    • 2018-02-13
    相关资源
    最近更新 更多