无法在另一个命名空间 (Raku) 中插入变量

Can't Interpolate Variable In Another Namespace (Raku)

我正在尝试使用插值在另一个命名空间中创建一个变量,但在咨询 https://docs.raku.org/language/packages#index-entry-::() 后无法让它工作。在测试不同的东西时,我发现了一些让我感到困惑的东西。这有效:

#!/usr/bin/env raku

$Foo::bar = 'foobar';
say $Foo::bar;
$ ./package.raku 
foobar

但这不是:

#!/usr/bin/env raku

my $bar = 'bar';
$Foo::($bar) = 'foobar';
say $Foo::bar;
$ ./package-interpolate.raku 
No such symbol '$bar'
  in block <unit> at ./package-interpolate.raku line 4

我错过了什么吗?谢谢!

TL;DR 对于某个特定包(名称空间)中保存的变量(符号)的引用,存在三种不同的语法。您尝试了两个,但看起来您可能需要第三个。这里也有一些皱纹,所以这个答案很长。


这里是三种语法的总结,从 可能 是您想要的(部分)语法开始,然后是您尝试过的两种语法:


  • Foo::Bar::Baz::<$qux>

    静态指定包,动态指定变量

    如果嵌套包规范无法解析为相应的一组现有包,则构造会产生错误。如果包规范解析,则创建变量(如果它已分配或绑定到并且尚不存在)。


  • $Foo::Bar::Baz::qux

    静态指定包和变量

    如果嵌套包规范中的任何包无法解析为现有包,则会自动创建该包。如果变量被赋值或绑定并且尚不存在,也会创建该变量。


  • ::('$Foo::Bar::Baz::qux')

    动态指定包和变量

    如果嵌套包规范无法解析为相应的一组现有包,或者如果变量不存在,则构造会产生错误。


此答案的其余部分是三个部分,其中包含与上述内容相对应的更多详细信息。

Foo::Bar::Baz::<$qux>

Staticically specified packages, dynamically specified variable

  • 如果所有包都已经存在,构造将起作用,如果变量不存在则创建变量。否则构造将产生一个错误,一个合理的 compile-time 错误(如果只指定了一个包)或 run-time 的一个 LTA 错误(如果指定了嵌套包)。

  • 变量名可以通过 expression/variable 间接指定,其值在 run-time.

    处求值

引用本节标题中链接的文档:

To do direct lookup in a package's symbol table without scanning, treat the package name as a hash

这是我们在 non-nested non-existent 包中引用 non-existent 变量的第一个示例:

my $bar = '$baz';
say Foo::{$bar};

产生编译时间错误:

Undeclared name:
    Foo used at line 2

接下来,一组嵌套non-existent包中的一个non-existent变量:

my $bar = '$baz';
try say Foo::Bar::Baz::{$bar};
say $!;               # Could not find symbol '&Baz' in 'GLOBAL::Foo::Bar'
say GLOBAL::Foo::Bar; # (Bar)

错误消息($! 中的异常)不仅仅是 LTA,而是反映了疯狂。构造创建了包 GLOBAL::Foo::Bar。更多疯狂!


现在举一个例子,其中我们引用了一个 确实 存在的包和其中一个 最初存在的变量:

package Foo {}
my $bar = '$qux';
say Foo::{$bar};              # (Any)
say Foo::{$bar}:exists;       # False
Foo::{$bar} = 99;
say Foo::{$bar}:exists;       # True
say Foo::{$bar};              # 99
say Foo.WHO.keys;             # ($qux)
say $Foo::qux;                # 99

因此,如果指定的变量不存在,Foo::{...} 语法(或 Foo::<...>,但不是 ::(...))会 not 出错,但仅当 软件包 不存在时才有效。

此语法中对不存在的变量的引用(在存在的包中)return是 (Any)。如果 (Any) 被赋值,它会创建变量。

(除非包是 MY 或未指定,这在此语法中意味着相同的事情。如果在 MY 中查找的变量丢失,查找将 return 值Nil 而不是 (Any),并且不能分配或绑定到。)


$Foo::Bar::Baz::qux

Statically specified packages and variable

  • 如果你提到一个使用这种语法的包,即使是non-nested一个包,如果它不存在你也会创建它。

  • 如果您 assign/bind 一个变量,如果它不存在,您将创建它。

  • 您必须静态说明您要指定的(嵌套)包和变量名。您不能间接声明(例如通过变量)此语法的任何部分。


引用本节标题中链接的文档:

Ordinary package-qualified names look like this: $Foo::Bar::quux, which would be the $quux variable in package Foo::Bar

这是您问题中的第一个代码片段(有效,@user0721090601 感到惊讶)附加了一些行。三行代码块显示了 Foo 包的着陆点(并提供了对该答案和文档中涵盖的三种语法的先睹为快)。最后一行确认 $bar 变量已添加到 Foo 包中:

$Foo::bar = 'foobar';
say $Foo::bar;             # foobar

say GLOBAL::Foo;           # (Foo)    Package qualified name yields type object `(Foo)`
say GLOBAL::<Foo>;         # (Foo)    Package qualified lookup (yields same object)
say ::('Foo');             # (Foo)    Package scanning lookup (yields same object)

say GLOBAL::Foo.WHO.keys;  # ($bar)   `.WHO` returns `(Foo)`'s package named `Foo`

GLOBAL 是包含“Interpreter-wide 包符号的 "pseudo" package,实际上是 UNIT::GLOBAL”。


如果你去掉上面代码中的第一行($Foo::bar = 'foobar';):

  • say $Foo::bar; 行的结果是 (Any);

  • 添加的三行代码块继续显示(Foo),表明仅提及句法形式的变量[=44] =] 足以创建包 Foo;

  • 最后一行显示(),表明 $bar 添加到 Foo 包中,尽管 say $Foo::bar; 行显示 (Any) 而不是Nil.


::('$Foo::Bar::Baz::qux')

Dynamically specified packages and variable

  • 如果您希望在现有包中读取或写入现有变量,而不会有意外创建它们的风险如果它们不存在,请使用此语法。

  • 相反,如果您希望自动创建包 and/or 变量,请使用其他两种语法之一或其他方法。

  • 您可以在此语法的 (...) 部分使用复杂的表达式,它们将被动态插入,但随后被视为静态源代码。因此,您可以执行一些操作,例如在 (...) 中包含文字字符串 '$Foo::Bar::Baz::qux' 或计算结果为此类字符串的变量,编译器会将其插入到整体引用中,然后用于指导查找,将 :: 之间的每个组件视为不同的嵌套包。


引用本节标题中链接的文档:

using ::($expr) where you'd ordinarily put a package or variable name ... the indirect name is looked up ... with priority given first to leading pseudo-package names, then to names in the lexical scope (searching scopes outwards, ending at CORE). The current package is searched last.

请注意,查找将扫描许多个包。


您问题中的 second 代码片段(not 按您的预期工作)使用此扫描逻辑进行变量查找,可能不仅仅查看一个明确指定的包,而是查看许多包。

这是相同的代码,但在开头插入了另一行:

package Foo { our $bar }     # <-- This line added
my $bar = 'bar';
$Foo::($bar) = 'foobar';
say $Foo::bar;               # foobar

现在可以了。

这表明您的 $Foo::($bar) = 'foobar'; 行:

  • 在包 Foo 中查找 变量 $bar。 (所以“插入名称”确实 像文档所宣传的那样工作。)

  • 但是没有创建包。

  • 并且没有创建变量。

  • 但是确实进行了赋值,因为查找成功找到了匹配查找的包和变量。

在 Raku 中使用插值在其他命名空间中创建变量似乎是不可能的。 如果它们事先在词法范围内,则可以通过这种方式访问​​它们。 如果您在代码之前添加此部分,它会起作用:

class Foo {
    our $bar = 'our $bar';
}

为了实现你想做的事情,我建议你查看 rosettacode 的这一部分: https://rosettacode.org/wiki/Add_a_variable_to_a_class_instance_at_runtime#Raku

它提出了几种不同的方法来解决 Raku 的这个问题。即使在他们的代码中“命名空间”的概念被简化为 类.