了解 Raku 的“&?BLOCK”编译时变量
Understanding Raku's `&?BLOCK` compile-time variable
我真的很欣赏 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)阻止二而不是阻止一?将 OUTER
与 $a
一起正确使用会导致它引用外部范围,但同样的事情不适用于 &?BLOCK
。 OUTER
与 &?BLOCK
一起使用是不可能的吗?如果没有,有没有办法从内部块访问外部块? (我知道我可以将 &?BLOCK
分配给外部块中的命名变量,然后在内部块中访问该变量。我认为这是一种解决方法,但不是完整的解决方案,因为它牺牲了引用未命名的能力块,这是 &?BLOCK
的大部分能量来源。)
其次,我对第2部分很困惑。我理解为什么前缀for后面的&?BLOCK
指的是一个内部块。但是为什么 后缀之前的 &?BLOCK
也 引用它自己的块?是否在 for 语句的主体周围隐式创建了一个块?我的理解是后缀形式在很大程度上是有用的,因为它们不需要块。这不正确吗?
最后,为什么有的块有OUTER::<$_>
,有的却没有?我对第 2 块感到特别困惑,它 不是 最外面的块。
提前感谢您提供的任何帮助! (如果上面显示的任何代码行为表明存在 Rakudo 错误,我很乐意将其写为问题。)
这是您遇到的一些非常令人困惑的事情。也就是说,它确实有某种意义......
Why does the &?OUTER::BLOCK
refer (based on its ID) to block two rather than block one?
根据文档,&?BLOCK
是一个“特殊的 compile 变量”,所有以 ?
为分支的变量都是如此.
因此,它不是一个可以在 运行-time 中查找的符号,这就是像 $FOO::bar
这样的语法应该是关于阿法克
所以我认为编译器应该有权拒绝在包查找语法中使用“编译变量”。 (虽然我不确定。在 COMPILING
包中进行“运行 次”查找是否有意义?)
可能已经有一个错误归档(在 GH 存储库 rakudo/rakudo/issues 或 raku/old-issues-tracker/issues 中)关于尝试对 运行-time 查找是错误的特殊编译变量(带有 ?
twigil 的变量)。如果没有,我可以提交一份。
Using OUTER
with $a
correctly causes it to refer to the outer scope
与外部块中的$a
变量关联的符号存储在与外部块关联的stash中。这是 OUTER
.
引用的内容
Is it just not possible to use OUTER
with &?BLOCK
?
我认为不是因为上面给出的原因。看看有没有人指正。
If not, is there a way to access the outer block from the inner block?
您可以将其作为参数传递。换句话说,用 }(&?BLOCK);
而不是 }
关闭内部块。然后你可以在内部块中以 $_
的形式使用它。
Why does the &?BLOCK
that precedes the postfix for
also refer to its own block?
直到你知道为什么,这才令人惊讶,但是......
Is a block implicitly created around the body of the for
statement?
似乎是这样,所以正文可以接受 for
.
的每次迭代传递的参数
My understanding is that the postfix forms were useful in large part because they do not require blocks.
我一直认为它们的好处是 A) 避免单独的词法作用域和 B) 避免必须输入大括号。
Is that incorrect?
好像是。 for
必须能够为其语句提供不同的 $_
(您可以将一系列语句放在括号中),因此如果您不显式编写大括号,它仍然必须创建一个独特的词汇框架,并且可能认为 &?BLOCK
变量用它自己的 $_
跟踪那个不同的框架并“假装”这是一个“块”,并用{...}
,尽管没有明确的 {...}
。
Why do some of the blocks have OUTER::<$_>
in them but others do not?
虽然 for
(和 given
等)总是将“它”又名 $_
参数传递给它的 blocks/statements,但其他块 不会 有一个参数 自动 传递给他们,但是 他们将接受一个 如果它是由代码编写者手动传递的.
为了支持可以传递或不传递参数的这种奇妙的习惯用法,除了自动提供 $_
的块之外,还提供了此 默认值 将 $_
绑定到外部块的 $_
.
I'm especially confused by Block 2, which is not the outermost block.
我很困惑你对此特别困惑。 :) 如果上述内容没有为您充分阐明最后一个方面,请评论最后一点特别令人困惑的地方。
在编译期间,编译器必须跟踪各种事情。其中之一是它正在编译的当前块。
块对象在它看到特殊变量的任何地方都会存储在编译代码中 $?BLOCK
。
基本上编译时变量并不是真正的变量,更像是一个宏。
因此,只要它看到 $?BLOCK
,编译器就会将其替换为编译器当前正在编译的任何当前块。
碰巧 $?OUTER::BLOCK
在某种程度上与 $?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
在后缀for
的情况下,左边需要是一个lambda/closure以便for
可以设置$_
为当前值。
将其伪造成块可能比仅为该用途创建新的代码类型更容易。
特别是因为整个 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 $_ }, $_ ); # 010203040
}
对于 call-it-a
,我们需要它作为 $_
的闭包才能工作。
对于 call-it-b
,我们需要它作为参数。
通过将 :( ;; $_? is raw = OUTER::<$_> )
作为签名,它可以满足两种用例。
这使得创建简单的 lambda 变得容易,这些 lambda 只做您想让它们做的事。
我真的很欣赏 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)阻止二而不是阻止一?将 OUTER
与 $a
一起正确使用会导致它引用外部范围,但同样的事情不适用于 &?BLOCK
。 OUTER
与 &?BLOCK
一起使用是不可能的吗?如果没有,有没有办法从内部块访问外部块? (我知道我可以将 &?BLOCK
分配给外部块中的命名变量,然后在内部块中访问该变量。我认为这是一种解决方法,但不是完整的解决方案,因为它牺牲了引用未命名的能力块,这是 &?BLOCK
的大部分能量来源。)
其次,我对第2部分很困惑。我理解为什么前缀for后面的&?BLOCK
指的是一个内部块。但是为什么 后缀之前的 &?BLOCK
也 引用它自己的块?是否在 for 语句的主体周围隐式创建了一个块?我的理解是后缀形式在很大程度上是有用的,因为它们不需要块。这不正确吗?
最后,为什么有的块有OUTER::<$_>
,有的却没有?我对第 2 块感到特别困惑,它 不是 最外面的块。
提前感谢您提供的任何帮助! (如果上面显示的任何代码行为表明存在 Rakudo 错误,我很乐意将其写为问题。)
这是您遇到的一些非常令人困惑的事情。也就是说,它确实有某种意义......
Why does the
&?OUTER::BLOCK
refer (based on its ID) to block two rather than block one?
根据文档,&?BLOCK
是一个“特殊的 compile 变量”,所有以 ?
为分支的变量都是如此.
因此,它不是一个可以在 运行-time 中查找的符号,这就是像 $FOO::bar
这样的语法应该是关于阿法克
所以我认为编译器应该有权拒绝在包查找语法中使用“编译变量”。 (虽然我不确定。在 COMPILING
包中进行“运行 次”查找是否有意义?)
可能已经有一个错误归档(在 GH 存储库 rakudo/rakudo/issues 或 raku/old-issues-tracker/issues 中)关于尝试对 运行-time 查找是错误的特殊编译变量(带有 ?
twigil 的变量)。如果没有,我可以提交一份。
Using
OUTER
with$a
correctly causes it to refer to the outer scope
与外部块中的$a
变量关联的符号存储在与外部块关联的stash中。这是 OUTER
.
Is it just not possible to use
OUTER
with&?BLOCK
?
我认为不是因为上面给出的原因。看看有没有人指正。
If not, is there a way to access the outer block from the inner block?
您可以将其作为参数传递。换句话说,用 }(&?BLOCK);
而不是 }
关闭内部块。然后你可以在内部块中以 $_
的形式使用它。
Why does the
&?BLOCK
that precedes the postfixfor
also refer to its own block?
直到你知道为什么,这才令人惊讶,但是......
Is a block implicitly created around the body of the
for
statement?
似乎是这样,所以正文可以接受 for
.
My understanding is that the postfix forms were useful in large part because they do not require blocks.
我一直认为它们的好处是 A) 避免单独的词法作用域和 B) 避免必须输入大括号。
Is that incorrect?
好像是。 for
必须能够为其语句提供不同的 $_
(您可以将一系列语句放在括号中),因此如果您不显式编写大括号,它仍然必须创建一个独特的词汇框架,并且可能认为 &?BLOCK
变量用它自己的 $_
跟踪那个不同的框架并“假装”这是一个“块”,并用{...}
,尽管没有明确的 {...}
。
Why do some of the blocks have
OUTER::<$_>
in them but others do not?
虽然 for
(和 given
等)总是将“它”又名 $_
参数传递给它的 blocks/statements,但其他块 不会 有一个参数 自动 传递给他们,但是 他们将接受一个 如果它是由代码编写者手动传递的.
为了支持可以传递或不传递参数的这种奇妙的习惯用法,除了自动提供 $_
的块之外,还提供了此 默认值 将 $_
绑定到外部块的 $_
.
I'm especially confused by Block 2, which is not the outermost block.
我很困惑你对此特别困惑。 :) 如果上述内容没有为您充分阐明最后一个方面,请评论最后一点特别令人困惑的地方。
在编译期间,编译器必须跟踪各种事情。其中之一是它正在编译的当前块。
块对象在它看到特殊变量的任何地方都会存储在编译代码中 $?BLOCK
。
基本上编译时变量并不是真正的变量,更像是一个宏。
因此,只要它看到 $?BLOCK
,编译器就会将其替换为编译器当前正在编译的任何当前块。
碰巧 $?OUTER::BLOCK
在某种程度上与 $?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
在后缀for
的情况下,左边需要是一个lambda/closure以便for
可以设置$_
为当前值。
将其伪造成块可能比仅为该用途创建新的代码类型更容易。
特别是因为整个 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 $_ }, $_ ); # 010203040
}
对于 call-it-a
,我们需要它作为 $_
的闭包才能工作。
对于 call-it-b
,我们需要它作为参数。
通过将 :( ;; $_? is raw = OUTER::<$_> )
作为签名,它可以满足两种用例。
这使得创建简单的 lambda 变得容易,这些 lambda 只做您想让它们做的事。