问题描述
这个问题始于我试图弄清楚为什么在运行时创建的元件对EVAL
不可用。
out-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::bar
在Foobar::
Stash
中,但不在OUTER::
PseudoStash
中。因此,我的问题有两个:为什么在运行时创建的符号对EVAL
不可用,为什么在运行时创建的符号对PseudoStash
不可用?推荐答案
何时应答才是nAnswer?
虽然我很高兴我写了这篇文章,但我对它不满意,因为它是我对您问题的核心的回答,即为什么没有办法让EVAL
违反词汇符号在编译期间被冻结的原则。
aiui原因可以归结为A)避免危险的安全漏洞,以及B)在某些MONKEY
杂注下不值得考虑违规。
但这是否准确,以及讨论这一点,以及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的开头部分(直到
our $foo = 42;
)读到一个相当不相关的SO问题可能会有所帮助。)
您问题的初步答案
它们可用。
但是:
在运行时创建的符号只能在一些现有或新的符号表Hash中创建(也称为";stash";)(该存储必须有一些符号命名,而该符号只能在一些现有的或新的存储中创建,依此类推);
此类符号的存储必须是包存储(使用内置
Stash
类型),而不是词法存储(使用PseudoStash
类型)。代码中对包符号的任何引用都必须将包含的包命名为该引用的一部分。
$foo::bar = 42;
语句,在运行时在包中声明符号$bar
:$bar
符号将添加到与包foo
关联的包存储(Stash
);如果包
foo
及其关联的存储不存在,则会依次创建它,并将符号foo
添加到与包含$foo::bar = 42;
语句的包对应的现有包存储中。
$bar
符号,您必须编写$foo::bar
(或使用其他形式的包限定引用之一,如foo::<$bar>
)。您不能仅将其称为$bar
。语言/编译器使用内置PseudoStash
类型在编译时隐藏词法符号。
有关词汇性和包作用域/符号/存储(以及our
声明符和词汇包可以创建词汇性包等复杂情况)之间区别的基本原理的讨论,请参阅What is the difference between my class
and our class
?的答案。
所有关于存储
有两种内置存储类型:
PseudoStash
用于词法存储语言/编译器在编译时将(静态)词法符号添加到词法存储。用户代码只能使用作为其操作一部分的语言构造来间接修改词法存储(例如,my $foo
和our $bar
都将词法符号添加到词法存储)。Stash
用于包存储语言/编译器在编译时或运行时将包符号添加到包存储中。用户代码可以添加、删除或修改包存储和包符号。
词法隐藏
这些文件由语言/编译器管理,并在编译结束时冻结。
您可以添加整个新的词法存储,也可以添加到现有的词法存储,但只能使用语言精确控制下的语言构造,例如:
A
{ ... }
词汇范围。这将导致编译器创建与该作用域对应的新词法存储。package Foo {}
、use Foo;
、my Foo = 42;
等。作为编译这些语句的一部分,语言/编译器会将符号Foo
添加到与包含此类语句的最内部词法范围相对应的词法存储中。(对于前两个,它还将创建一个新的包存储并将其与Foo
符号的值相关联。可通过Foo.WHO
或Foo::
访问此存储。)
您可以使用MY
、OUTER
、CORE
和UNIT
等各种"pseudo-packages"来引用词汇表及其内部的符号。
您可以使用与词法存储关联的伪包将赋值或绑定绑定到这些词法存储中的现有符号:
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链接。)
包存储
程序包存储由语言/编译器根据用户代码根据需要创建。
与词法存储一样,您可以通过一些伪包和名称来引用包存储。
OUR:: | OUR 出现的范围 |
GLOBAL:: | 口译员 |
PROCESS:: | 解释器运行的进程 |
由于语言结构的隐式含义,如our
声明:
our $foo = 42;
这将向与最内部封闭词法作用域对应的词法存储以及与该作用域对应的包存储添加$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)
与词法存储不同,包存储是可修改的。从上面的代码继续:
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
声明符之前没有作用域声明符(例如my
或our
),则假定our
。因此,上面的代码将:
新建
Foo
符号; 安装两个创建新的包存储,并将其与
Foo
类型对象相关联,可通过编写Foo::
或Foo.WHO
访问该对象。
Foo
符号副本,一个在与最里面封闭词法作用域(可通过MY::
访问)对应的词法存储中,另一个在与该作用域(可通过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
词法存储中Foo
符号的值与OUR
包存储中的值完全相同。该值绑定到通过Foo.WHO
(又名Foo::
)访问的另一个包存储。
soMY::Foo::.keys
和OUR::Foo::.keys
列出相同的符号,即$baz
,在Foo
包的存储中。
您不要看到$bar
,因为它在词法存储中,该存储对应于与Foo
包相同的周围作用域,但仍然是一个不同的存储。更广泛地说,您不能从大括号代码外部看到,因为Raku设计的一个关键元素是,用户和编译器可以依赖纯粹的词汇范围的符号,由于它们的词汇性质,这些符号是100%封装的。
虽然您甚至无法从词法范围之外查看任何词法符号,但您不仅可以从任何您可以访问与其包含的包对应的符号的位置看到任何包符号,还可以修改任何包 类似 因为它们是s符号t启用hash,所以被称为package Foo { our sub qux { say $Foo::baz } }
$Foo::baz = 99;
Foo::qux; # 99
$Foo::Bar::Baz::qux = 99;
的行将在必要时自动激活任何不存在的包存储,然后可以使用包存储引用(如伪包OUR
:$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
脚注
抽象概念、符号和符号实现为Pair
存储在存储中。
这个术语可能有些不恰当,因为其中几个伪包是Stash
而不是PseudoStash
的别名。
对于Foo::Bar
这样不以符号开头但包含::
的引用,您需要确保遵守Raku的规则来解析此类引用。我仍在弄清楚这些答案到底是什么,并打算在确定答案后更新此答案,但我已决定在此期间发布此答案。
这篇关于运行时在Stash中创建的符号在Raku的PseudoStash中不可用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!