什么时候回答是nanswer?
虽然我很高兴我写了这篇文章,但我对它不满意因为我的结论是你问题的核心,即为什么 em> 没有办法让EVAL 违反编译过程中词法符号被冻结的原则。
Aiui 的基本原理归结为 A) 避免危险的安全漏洞,以及 B) 在某些 MONKEY pragma 下允许违规被认为是不值得的。
但是这是否准确,讨论这个,以及 Rakudo 插件可能违反词法原则的任何可能性,远远超出了我的薪酬等级。
我很想将此答案复制到要点中,从对您问题的评论中链接到它,然后删除此答案。
或者,如果您同意我的回答中存在这个巨大的漏洞,也许您会好心不接受它,然后我可以悬赏它以尝试从 jnthn 获得答案,和/或鼓励其他人回答?
快速修复
-
添加包限定符:
package Foo {
our sub outer {
EVAL 'say $Foo::bar' # Insert `Foo::`
}
}
$Foo::bar = 'bar';
Foo::outer; # bar
或:
-
使用词法(编译时)符号:
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<$foo>) = 42;”)对于一个相当不相关的 SO 问题可能会有所帮助。)
对您的问题的初步回答
为什么EVAL 无法在运行时创建符号?
他们是可用的。
但是:
-
在运行时创建的符号只能在一些现有的或新的符号表哈希(又名“stash”[1])中创建(其中 stash 必须有一些命名它的符号,而该符号又只能在一些现有或新的存储中创建,以此类推);
-
此类符号的存储必须是 package 存储(使用内置的 Stash 类型),而不是 lexical 存储(使用 PseudoStash 类型)。
-
代码中对 package 符号的任何引用都必须将封闭的包命名为该引用的一部分。
因此,例如,给定一个 $foo::bar = 42; 语句,在运行时在包 foo 中声明符号 $bar:
然后,要引用$bar 符号,您必须编写$foo::bar(或使用其他形式的包限定引用,例如foo::<$bar>)。您不能将其称为 $bar。
为什么PseudoStashs 无法在运行时创建符号?
语言/编译器使用内置的PseudoStash 类型在编译时存储词法符号。
讨论 lexical 和 package 作用域/符号/stashes 之间这种区别的基本原理(以及诸如our 声明符对两者的使用的复杂性;以及事实上可以创建词法包),请参阅What is the difference between my class and our class? 的答案。
关于隐藏
有两种内置的存储类型:
-
PseudoStashs 用于 lexical stashes 语言/编译器添加(静态)lexical 符号[2 ] 到编译时的词法存储。用户代码只能使用作为其操作的一部分的语言结构来间接修改词法存储(例如,my $foo 和 our $bar 都将词法符号添加到词法存储中)。
-
Stashs 用于 package stashes 语言/编译器将 package 符号(在编译时或运行时)添加到在编译时或运行时包存储。用户代码可以添加、删除或修改包存储和包符号。
词法隐藏
这些由语言/编译器管理并在编译结束时冻结。
您可以添加全新的词法存储,并添加到现有的词法存储中,但只能使用语言精确控制下的语言结构,例如:
-
{ ... } 词法范围。这将导致编译器创建一个与作用域对应的新词法存储。
-
package Foo {}、use Foo;、my \Foo = 42; 等。作为编译这些语句的一部分,语言/编译器将添加符号 Foo 到对应于包含此类语句的最内层词法范围的词法存储区。 (对于前两个,它还将创建一个新的 package 存储并将其与Foo 符号的值相关联。此存储可通过Foo.WHO 或Foo:: 访问。)
您可以通过使用各种 "pseudo-packages"[3](例如 @987654368)引用词法存储以及其中的符号@、OUTER、CORE 和 UNIT。
您可以使用与词法存储关联的伪包分配或绑定到这些词法存储中的现有符号:
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 声明符之前没有范围声明符(例如 my 或 our),则假定为 our。因此上面的代码会:
因此,尽管最初有任何意外,但现在这有望变得有意义:
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::.keys 和OUR::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 的解决此类引用的规则。我仍在弄清楚这些到底是什么,并打算在确定后更新这个答案,但我决定在此期间按原样发布这个答案。