cod*_*ons 11 metaprogramming rakudo compile-time raku
我真的很欣赏 Raku 的&?BLOCK变量——它让你在一个未命名的块内递归,这可能非常强大。例如,这是一个简单的内联匿名阶乘函数:
{ when $_ ? 1 { 1 };
$_ × &?BLOCK($_ - 1) }(5) # OUTPUT: «120»
Run Code Online (Sandbox Code Playgroud)
但是,在更复杂的情况下使用时,我对它有一些疑问。考虑这个代码:
{ 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 }
};
Run Code Online (Sandbox Code Playgroud)
产生这个输出(我已经缩短了块 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) ... }
Run Code Online (Sandbox Code Playgroud)
这就是我不明白的地方:为什么&?OUTER::BLOCK引用(基于其 ID)阻止两个而不是阻止一个?正确使用OUTERwith$a会导致它引用外部范围,但同样的事情不适用于&?BLOCK. 只是不能OUTER与一起使用&?BLOCK吗?如果没有,有没有办法从内部块访问外部块?(我知道我可以&?BLOCK在外部块中分配一个命名变量,然后在内部块中访问该变量。我认为这是一种解决方法,但不是一个完整的解决方案,因为它牺牲了引用未命名块的能力,这就是的大部分&?BLOCK力量来自。)
其次,我对第 2 部分感到非常困惑。我理解为什么&?BLOCK前缀 for 后面的 指的是内部块。但是为什么&?BLOCK在后缀 for 之前的也指的是它自己的块呢?是否在 for 语句的主体周围隐式创建了一个块?我的理解是,后缀形式,因为它们都在很大程度上有用不要求块。那不正确吗?
最后,为什么有些块OUTER::<$_>在 中而其他块没有?我对 Block 2 尤其困惑,它不是最外面的块。
在此先感谢您提供的任何帮助!(如果上面显示的任何代码行为表明存在 Rakudo 错误,我很乐意将其写成一个问题。)
这是您遇到的一些非常令人困惑的事情。就是说,这一切都有些道理……
为什么
&?OUTER::BLOCK引用(基于其 ID)阻止两个而不是阻止一个?
根据文档,&?BLOCK是一个“特殊的编译变量”,就像所有将 a?作为它们的 twigil 的变量一样。
因此,它不是一个可以在run-time 中查找的符号,这$FOO::bar应该是关于 afaik 的语法。
所以我认为编译器应该有权拒绝使用带有包查找语法的“编译变量”。(虽然我不确定。在COMPILING包中进行“运行时”查找有意义吗?)
可能已经存在一个错误(在 GH repos rakudo/rakudo/issues 或 raku/old-issues-tracker/issues 中)关于尝试对特殊编译变量进行运行时查找是错误的(那些有?树枝的)。如果没有,提交一份对我来说是有意义的。
正确使用
OUTERwith$a会导致它引用外部作用域
与$a外部块中的变量关联的符号存储在与外部块关联的存储中。这就是引用的内容OUTER。
只是不能
OUTER与一起使用&?BLOCK吗?
我认为不是因为上面给出的原因。让我们看看有没有人纠正我。
如果没有,有没有办法从内部块访问外部块?
您可以将其作为参数传递。换句话说,使用}(&?BLOCK);而不是仅仅关闭内部块}。然后你就可以像$_在内部块中一样使用它。
为什么
&?BLOCK后缀之前的for也指的是它自己的块?
直到你知道为什么,这才令人惊讶,但是......
是否在
for语句主体周围隐式创建了一个块?
看起来如此,所以主体可以接受 . 的每次迭代传递的参数for。
我的理解是后缀形式在很大程度上很有用,因为它们不需要块。
我一直认为他们的好处是他们 A) 避免了单独的词法范围和 B) 避免必须输入大括号。
那不正确吗?
似乎是这样。for必须能够$_为其语句提供一个不同的(您可以在括号中放置一系列语句),因此如果您不明确地编写大括号,它仍然必须创建一个不同的词法框架,并且大概是考虑更好的&?BLOCK变量跟踪与自己那独特的框架$_,这是一个“块”,“假装”,并与所显示的,其要旨{...},尽管那里是没有明确{...}。
为什么有些块中有
OUTER::<$_>而其他块没有?
虽然for(given等)总是将“it”又名$_参数传递给它的块/语句,但其他块没有自动传递给它们的参数,但如果代码编写者手动传递一个参数,它们将接受一个。
为了支持这一奇妙成语,其中一个可以通过或不通过一个参数,比那些其它块被自动送入一个$_被给予此默认结合$_到外块的$_。
我对 Block 2 尤其困惑,它不是最外面的块。
我很困惑你对此特别困惑。:) 如果上述内容还没有为您充分说明最后一个方面,请评论最后一点特别令人困惑的内容。
在编译期间,编译器必须跟踪各种事情。其中之一是它正在编译的当前块。
块对象被存储在编译代码中它看到特殊变量的任何地方$?BLOCK。
基本上编译时变量并不是真正的变量,而是更多的宏。
因此,每当它看到$?BLOCK编译器将其替换为编译器当前正在编译的任何当前块。
它只是碰巧$?OUTER::BLOCK在某种程度上足够接近$?BLOCK它也取代了它。
我可以通过尝试按名称查找来向您展示确实没有该名称的变量。
{ say ::('&?BLOCK') } # ERROR: No such symbol '&?BLOCK'
Run Code Online (Sandbox Code Playgroud)
此外,每一对{}(不是哈希引用或哈希索引)都表示一个新块。
所以每一行都会说一些不同的东西:
{
say $?BLOCK.WHICH;
say "{ $?BLOCK.WHICH }";
if True { say $?BLOCK.WHICH }
}
Run Code Online (Sandbox Code Playgroud)
这意味着如果您在这些构造之一中声明一个变量,它就会包含在该构造中。
"{ 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
Run Code Online (Sandbox Code Playgroud)
在 postfix 的情况下for,左侧需要是一个 lambda/closure,以便for可以设置$_为当前值。将它伪装成一个 Block 可能比创建一个专门用于该用途的新 Code 类型更容易。
特别是因为整个 Raku 源文件也被认为是一个块。
一个裸块可以有一个可选的参数。
my &foo;
given 5 {
&foo = { say $_ }
}
foo( ); # 5
foo(42); # 42
Run Code Online (Sandbox Code Playgroud)
如果你给它一个参数,它就会设置$_为那个值。
如果不这样做,$_将指向$_该声明之外的任何内容。(关闭)
对于该构造的许多用途,这样做非常方便。
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?
}
Run Code Online (Sandbox Code Playgroud)
因为call-it-a我们需要它作为结束$_工作。
因为call-it-b我们需要它成为一个论点。
通过将其:( ;; $_? is raw = OUTER::<$_> )作为签名,它可以满足这两种用例。
这使得创建简单的 lambda 变得容易,这些 lambda 只做你想让他们做的事情。