11

我真的很欣赏 Raku 的&?BLOCK变量——它可以让你在一个未命名的块中递归,这非常强大。例如,这是一个简单的内联匿名阶乘函数:

{ when $_ ≤ 1 { 1 }; 
  $_ × &?BLOCK($_ - 1) }(5) # OUTPUT: «120»

但是,在更复杂的情况下使用它时,我有一些疑问。考虑这段代码:

{   say "Part 1:";
    my $a = 1;
    print '    var one: '; dd $a;
    print '  block one: '; dd &?BLOCK ;
    {
        my $a = 2;
        print '    var two: '; dd $a;
        print '  outer var: '; dd $OUTER::a;

        print '  block two: '; dd &?BLOCK;
        print "outer block: "; dd &?OUTER::BLOCK
    }
    say "\nPart 2:";
    print '  block one: '; dd &?BLOCK;
    print 'postfix for: '; dd &?BLOCK for (1);
    print ' prefix for: '; for (1) { dd &?BLOCK }
};

这会产生这个输出(我已经缩短了块 ID):

Part 1:
    var one: Int $a = 1
  block one: -> ;; $_? is raw = OUTER::<$_> { #`(Block|…6696) ... }
    var two: Int $a = 2
  outer var: Int $a = 1
  block two: -> ;; $_? is raw = OUTER::<$_> { #`(Block|…8496) ... }
outer block: -> ;; $_? is raw = OUTER::<$_> { #`(Block|…8496) ... }

Part 2:
  block one: -> ;; $_? is raw = OUTER::<$_> { #`(Block|…6696) ... }
postfix for: -> ;; $_ is raw { #`(Block|…9000) ... }
 prefix for: -> ;; $_ is raw { #`(Block|…9360) ... }

这就是我不明白的地方:为什么&?OUTER::BLOCK引用(基于其ID)阻止两个而不是阻止一个?正确使用OUTERwith$a会导致它引用外部范围,但同样的事情不适用于&?BLOCK. 只是不能使用OUTERwith&?BLOCK吗?如果没有,有没有办法从内部块访问外部块?(我知道我可以分配&?BLOCK给外部块中的命名变量,然后在内部块中访问该变量。我认为这是一种解决方法,但不是完整的解决方案,因为它牺牲了引用未命名块的能力,这就是的大部分&?BLOCK力量来自于。)

其次,我对第 2 部分感到非常困惑。我明白为什么&?BLOCK前缀 for 后面的那个是指内部块。但是为什么&?BLOCK在后缀 for 之前的那个指的是它自己的块呢?是否在 for 语句的主体周围隐式创建了一个块?我的理解是后缀形式在很大程度上很有用,因为它们不需要块。这是不正确的吗?

最后,为什么有些块OUTER::<$_>在其中而有些则没有?我对第 2 块特别困惑,它不是最外面的块。

提前感谢您提供的任何帮助!(如果上面显示的任何代码行为表明存在 Rakudo 错误,我很乐意将其写为问题。)

4

2 回答 2

6

这是您遇到的一些非常令人困惑的事情。也就是说,这确实有某种意义......

为什么&?OUTER::BLOCK引用(基于其 ID)阻止两个而不是阻止一个?

根据文档,&?BLOCK是一个“特殊的编译变量”,就像所有以 a?作为它们的 twigil 的变量一样。

因此,它不是一个可以在运行时查找的符号,这就是$FOO::barafaik 的语法。

所以我认为编译器应该正确地拒绝使用带有包查找语法的“编译变量”。(虽然我不确定。在COMPILING包中进行“运行时”查找是否有意义?)

可能已经提交了一个错误(在 GH repos rakudo/rakudo/issues 或 raku/old-issues-tracker/issues 中)关于尝试对特殊编译变量进行运行时查找是错误的(那些有?树枝的)。如果没有,我提交一份文件对我来说是有意义的。

正确使用OUTERwith$a会导致它引用外部范围

$a与外部块中的变量关联的符号存储在与外部块关联的存储中。这就是OUTER.

只是不能使用OUTERwith&?BLOCK吗?

我认为不是因为上面给出的原因。让我们看看是否有人纠正我。

如果没有,有没有办法从内部块访问外部块?

您可以将其作为参数传递。换句话说,使用}(&?BLOCK);而不是 just关闭内部块}。然后你可以$_在内部块中使用它。

为什么&?BLOCK后缀前面的那个for也指它自己的块?

在您知道原因之前,这令人惊讶,但是...

for是否在语句主体周围隐式创建了一个块?

似乎是这样,因此主体可以接受由for.

我的理解是后缀形式在很大程度上很有用,因为它们不需要块。

我一直认为它们的好处是它们 A) 避免了单独的词法范围和 B) 避免输入大括号。

这是不正确的吗?

似乎是这样。for必须能够$_为其语句提供不同的(您可以将一系列语句放在括号中),因此如果您不明确编写大括号,它仍然必须创建一个不同的词法框架,并且大概是尽管没有&?BLOCK明确$_的.{...}{...}

为什么有些方块里面有OUTER::<$_>,而有些没有?

虽然forgiven等)总是将“它”又名$_参数传递给它的块/语句,但其他块没有自动传递给它们的参数,但如果它是由代码编写者手动传递的,它们将接受一个手动传递一个。

为了支持这种可以传递或不传递参数的美妙习惯,除了自动输入 an 的块之外,其他块$_默认绑定$_到外部块的$_.

我对第 2 块特别困惑,它不是最外面的块。

我很困惑你对此特别困惑。:) 如果上述内容还没有为您充分清除最后一个方面,请评论关于这最后一点特别令人困惑的内容。

于 2020-07-25T23:39:28.350 回答
6

在编译期间,编译器必须跟踪各种事情。其中之一是它正在编译的当前块。

块对象在它看到特殊变量的任何地方都存储在已编译的代码中$?BLOCK

基本上,编译时变量并不是真正的变量,而更像是一个宏。

因此,每当它看到$?BLOCK编译器将其替换为编译器当前正在编译的任何当前块时。

它只是碰巧$?OUTER::BLOCK在某种程度上足够接近$?BLOCK它也取代了它。

我可以通过尝试按名称查找来向您展示该名称确实没有变量。

{ say ::('&?BLOCK') } # ERROR: No such symbol '&?BLOCK'

此外,每一对{}(不是哈希引用或哈希索引)都表示一个新块。

所以这些行中的每一行都会说不同的东西:

{
  say $?BLOCK.WHICH;
  say "{ $?BLOCK.WHICH }";
  if True { say $?BLOCK.WHICH }
}

这意味着,如果您在其中一个构造中声明一个变量,它将包含在该构造中。

"{ my $a = "abc"; say $a }"; # abc
say $a; # COMPILE ERROR: Variable '$a' is not declared

if True { my $b = "def"; say $b } # def
say $b; # COMPILE ERROR: Variable '$b' is not declared

在 postfix 的情况下for,左侧需要是一个 lambda/closure 以便for可以设置$_为当前值。将其伪装成 Block 可能比仅为该用途创建新的 Code 类型更容易。
特别是因为整个 Raku 源文件也被视为一个块。


裸块可以有一个可选参数。

my &foo;

given 5 {
  &foo = { say $_ }
}

foo(  ); # 5
foo(42); # 42

如果你给它一个参数,它会设置$_为那个值。
如果你不这样做,$_将指向$_该声明之外的任何内容。(关闭)

对于该构造的许多用途,这样做非常方便。

sub call-it-a (&c){
  c()
}
sub call-it-b (&c, $arg){
  c( $arg * 10 )
}

for ^5 {
  call-it-a( { say $_ }     ); # 0␤ 1␤ 2␤ 3␤ 4␤
  call-it-b( { say $_ }, $_ ); # 0␤10␤20␤30␤40␤
}

因为call-it-a我们需要它来结束$_工作。
因为call-it-b我们需要它作为一个论点。

通过将其:( ;; $_? is raw = OUTER::<$_> )作为签名,它可以满足两种用例。

这使得创建简单的 lambda 表达式变得很容易,它们只做你想让它们做的事情。

于 2020-07-26T04:11:34.817 回答