本文介绍了运行时在Stash中创建的符号在Raku的PseudoStash中不可用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这个问题始于我试图弄清楚为什么在运行时创建的元件对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::barFoobar::Stash中,但不在OUTER::PseudoStash中。因此,我的问题有两个:为什么在运行时创建的符号对EVAL不可用,为什么在运行时创建的符号对PseudoStash不可用?

推荐答案

何时应答才是nAnswer?

虽然我很高兴我写了这篇文章,但我对它不满意,因为它是我对您问题的核心的回答,即为什么没有办法让EVAL违反词汇符号在编译期间被冻结的原则。

aiui原因可以归结为A)避免危险的安全漏洞,以及B)在某些MONKEY杂注下不值得考虑违规。

但这是否准确,以及讨论这一点,以及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的开头部分(直到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 $fooour $bar都将词法符号添加到词法存储)。

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

词法隐藏

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


您可以添加整个新的词法存储,也可以添加到现有的词法存储,但只能使用语言精确控制下的语言构造,例如:

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

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


您可以使用MYOUTERCOREUNIT等各种"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声明符之前没有作用域声明符(例如myour),则假定our。因此,上面的代码将:

  • 新建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)

MY词法存储中Foo符号的值与OUR存储中的值完全相同。该值绑定到通过Foo.WHO(又名Foo::)访问的另一个存储。

soMY::Foo::.keysOUR::Foo::.keys列出相同的符号,即$baz,在Foo包的存储中。

不要看到$bar,因为它在词法存储中,该存储对应于与Foo包相同的周围作用域,但仍然是一个不同的存储。更广泛地说,您不能从大括号代码外部看到,因为Raku设计的一个关键元素是,用户和编译器可以依赖纯粹的词汇范围的符号,由于它们的词汇性质,这些符号是100%封装的。


虽然您甚至无法从词法范围之外查看任何词法符号,但您不仅可以从任何您可以访问与其包含的包对应的符号的位置看到任何符号,还可以修改任何

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

脚注

因为它们是s符号t启用hash,所以被称为

抽象概念、符号和符号实现为Pair存储在存储中。

这个术语可能有些不恰当,因为其中几个伪包是Stash而不是PseudoStash的别名。

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

这篇关于运行时在Stash中创建的符号在Raku的PseudoStash中不可用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

10-30 17:22