关于Raku中EVAL中哈希绑定的问题

8

我在使用 EVAL 绑定哈希表时遇到了一些不理解的问题。在 EVAL 外部绑定哈希表与预期相符。未绑定哈希表的 EVAL 也如预期工作。但是,在 EVAL 内部绑定哈希表并不按照我的预期工作。(我的预期可能有误。)以下是代码:

这行代码可正常工作:

#!/usr/bin/env raku

class Hash::Test does Associative {
  has %.hash;

  multi method STORE(@pairs) {
    for @pairs -> $pair {
      self.STORE: $pair
    }
  }

  multi method STORE(Pair $pair) {
    %!hash{$pair.key} = $pair.value;
  }
}

no strict;
%hash-test := Hash::Test.new;
%hash-test = foo => 'bar', baz => 'quux';
say %hash-test;

输出:

$ ./hash-binding-works.raku 
Hash::Test.new(hash => {:baz("quux"), :foo("bar")})

这是可行的:

#!/usr/bin/env raku

class Foo {
  use MONKEY-SEE-NO-EVAL;

  method eval(Str $code) {
    EVAL $code;
  }
}

my $code = q:to/END/;
  no strict;
  %hash = foo => 'bar', baz => 'quux';
  END

Foo.eval: $code;
say %Foo::hash;

输出:

$ ./hash-EVAL-works.raku 
{baz => quux, foo => bar}

但这样不起作用:

#!/usr/bin/env raku

class Hash::Test does Associative {
  has %.hash;

  multi method STORE(@pairs) {
    for @pairs -> $pair {
      self.STORE: $pair
    }
  }

  multi method STORE(Pair $pair) {
    %!hash{$pair.key} = $pair.value;
  }
}

class Foo {
  use MONKEY-SEE-NO-EVAL;

  method eval(Str $code) {
    EVAL $code;
  }
}

my $code = q:to/END/;
  no strict;
  %hash-test := Hash::Test.new;
  %hash-test = foo => 'bar', baz => 'quux';
  say %hash-test;
  END

no strict;
Foo.eval: $code;
say %Foo::hash-test;

输出:

$ ./hash-EVAL-does-not-work.raku 
Hash::Test.new(hash => {:baz("quux"), :foo("bar")})
{}

Hash::Test 不是我真正使用的类名,而是我缩减后的代码。有人能解释一下这里发生了什么吗?谢谢!


为什么你在使用 no strict?如果将其移除会发生什么? - Elizabeth Mattijsen
对我来说,如果我运行最后的代码但没有使用no strict;,我会得到“变量'%hash-test'未声明”。@ElizabethMattijsen - raiph
@raiph,感谢你指出这个问题。是的,那是复制/粘贴失败了。我已经纠正了它。 - JustThisGuy
@ElizabethMattijsen,@raiph所说的是正确的。我正在使用它来保持与最后一段不起作用的代码块的一致性。由于EVAL无法在周围范围内创建词法,因此我无法使用my - JustThisGuy
1个回答

6
TL;DR:使用no strict;会通过隐式的our声明符号在包中自动声明变量。这个声明符号使用隐式的my 词法变量声明符号来绑定具有相同名称的隐式包符号。如果代码打破了这种绑定,那么就会导致代码出错。为了解决这个问题,需要以另一种方式表达相同的意思。
解决方案:不要使用no strict;,也不要使用our。相反,应该声明一个my词法变量,并在需要时进行操作,最后,在将要被EVAL的代码末尾创建一个包变量,并将其绑定到存储在词法变量中的值。
my $code = q:to/END/;
  my %hash is Hash::Test; 
  %hash = foo => 'bar', baz => 'quux';
  OUR::<%hash-test> := %hash;
  END

Foo.eval: $code;
say %Foo::hash-test; # Hash::Test.new(hash => {:baz("quux"), :foo("bar")})

惊喜的解释

no strict;下声明的没有明确说明符的变量隐含地声明为our变量:

no strict;
%hash-test = :a;
say MY::<%hash-test>;  # {a => True}
say OUR::<%hash-test>; # {a => True}

换句话说,上述前两行的净效应等同于:
our %hash-test = :a;

反过来,我们的变量隐式声明了我的变量,并遵循这篇SO文章中所示的逻辑。所以这段代码:

no script;
%hash-test := ...;

正在进行以下操作:

(my %hash-test := $?PACKAGE.WHO<%hash-test>) := ...;

它创建了一个词法使用的 %hash-test 符号和封装在一个包内的 %hash-test 符号,并绑定它们 - 这个绑定对于正确地运行我们的变量是 必不可少 的——然后立即破坏了这个必要的绑定。

以后,无论您的其他代码执行了什么操作,它都只是针对变量的 词法 %hash-test 版本执行,使得这个符号版本的 %hash-test被遗弃,因此稍后就会自动变成空哈希。


正如我在下面链接的SO中所说的那样:

我们肯定可以警告绑定到`our` 变量是毫无意义的

但目前还没有任何警告。


正如您在下面的评论中解释的那样,当您尝试使用 %hash-test is Hash::Test 时,编译器神秘地决定您已经写了"两个连续的术语"。正如我在下面的评论中所解释的那样,这是由于使用常规语法(或使用 no strict; )声明our变量时所做的花招。


为了解决上述所有问题,请忘记 no strict; ,放弃使用 our,而是:

  • 使用词法语句设置值;

  • 最后,使用OUR::<%hash-test>创建包符号,并将其绑定到词法值。


1
在上一个例子中,我最初尝试做到这一点:%hash-test is Hash :: Test = foo => 'bar',baz => 'quux'; 但是得到了 Two terms in a row at /development/raku/VTS-Template/EVAL_0:2 ------>%hash-test⏏ is Hash::Test = foo => 'bar', baz => 'q expecting any of: infix infix stopper statement end statement modifier statement modifier loop 我是否正确地认为 is 在幕后绑定? - JustThisGuy
1
一个 is 是一个 trait routine,不同的 traits 做不同的事情。我不知道当跟着一个类或角色时,在变量上使用 is 在幕后做什么,但我知道它在编译时对一个 Variable 类型的对象进行操作,这与我们通常所认为的变量不同。但你遇到的错误与此无关。造成错误是因为我在答案中给出了什么。就像你写了 (my %hash-test := $?PACKAGE.WHO<%hash-test>) is Hash::Test = ...,这是两个连续的术语。 - raiph

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接