剖析一个 Raku 片段

Dissecting a Raku snippet

几天前,我在为一个问题的解决方案做原型时,写了一段代码,给我带来了巨大的喜悦。那是一种让你每次看到它运行时都会傻笑的代码,只因为这个解决方案看起来是多么的聪明和优雅,也因为证明了,与你的期望相反,它确实是可行的,而感到兴奋。

想分享这种兴奋,我把它分享到了网上。这个片段是这样的。

sub foo ( $a, $b ) { $a ~ $b }

my $wh;

$wh = &foo.wrap: {
    LEAVE &foo.unwrap: $wh;
    callwith( $^b, $^a );
}

say foo( 1, 2 ) for 1 .. 3;

这是非常密集的。

而且有点神秘

而且还很有趣

所以,让我们打破它。要做到这一点,我们将开始从结束,并努力回到开始的方式。

结果

当上面的代码得到执行时,它打印出以下几行。

21 12 12

这就是最后一行的结果。

say foo( 1, 2 ) for 1 .. 3;

其中打印了三次调用 foo( 1, 2 ) 的结果。这一行对于 Perl 程序员来说是很熟悉的,因为 Raku 支持"语句修饰符",它从 Perl 中继承了这种修饰符:语句末尾的 for 修改了前面的语句,并对 1 .. 3 范围内的每个元素(实际上是一个 Range 对象)执行一次。

但是如果我们调用 foo( 1, 2 ) 三次,那么为什么第一次调用它的时候输出的是 21,而其他时候输出的是 12 呢?

包装代码

那是因为之前的块:

my $wh;

$wh = &foo.wrap: {
    LEAVE &foo.unwrap: $wh;
    callwith( $^b, $^a );
}

这段代码是神奇的核心所在。

第一个重要的位子是对 wrap 的调用 这是在 foo 子程序对象上调用的(这就是为什么我们用 & sigil 来引用它,否则我们可能会调用函数,而不是得到对它的引用)。

wrap 方法允许我们将一个代码块附加到 foo 上,这样调用该函数将代替执行该代码块。

重新分派

在我们用来封装 foo 的代码块中,我们使用 callwith 来调用封装的子程序。我们可以使用许多不同的选择,这取决于我们是否想要得到一个返回值,是否想要修改底层函数被调用的参数。

在这种情况下,callwith 允许我们修改参数,但它从不返回。而我们修改参数的方法像 callwith( $^b, $^a ) 调用它。

但是这些变量是怎么来的呢?

自声明的定位符

它们是自我声明的位置参数

当一个代码块没有定义一个显式的参数列表时,在它的范围内使用 ^ twigil 的任何变量( = 任何在符号后面有 ^ 的变量)都会声明一个隐式的位置参数。块接收到的每个位置参数都会按照字母顺序被分配给这些变量。

那么在上面的例子中,调用 callwith( $^b, $^a ) 将重新分派到我们正在封装的函数,并将第一个和第二个参数进行切换。由于原来的子程序将两个参数连在一起(使用 ~ 操作符),这就解释了第一行输出:我们调用 foo( 1, 2 ),这将重新分派到 foo( 2, 1 ),结果是 21 行。

进步!

现在是真正神奇的一点。

自解封装器

在真正重新调度到原代码之前,我们还有一行。

LEAVE &foo.unwrap: $wh;

这一行使用 LEAVE 相位器来注册一个语句,在执行离开当前块时被调用。因此,在切换参数并重新派发到原子程序后,执行离开块,这条语句最终被执行。

当它被执行时,它会调用 foo 上的 unwrap 方法来删除包装代码(它通过传递存储在 $wh 变量中的包装句柄来实现)。

包裹

就是这样! 这段代码定义了一个 foo 子程序,它接受两个参数并将它们连接起来。然后将其封装到一个代码块中,在调用原始子程序之前交换参数,然后将其删除,就像它从未存在过一样。

这就解释了输出:我们第一次调用 foo( 1, 2 ) 时,参数被交换了,我们打印出 21,但是在随后的调用中,已经没有包装代码了,所以我们得到了12这样的行。

真是口水直流啊! 但所有这些都只需要七行代码。Raku 真的可以包装一拳。

这很好,但是真正的用处是什么呢?

顺便说一下,这是我为了解决一个实际问题而做的原型:我有一个回调会被反复调用,我想在第一次执行回调的时候挂入,这样我就可以运行一个初步检查。但是一旦检查通过,每次都做就没有意义了。 Unwrap 来救场了

顺便说一下,这利用了另一个有用的 Raku 特性:在 Raku 中,所有的东西都是一个对象。这就是为什么我们可以,例如,无缝调用子程序上的方法。 ↩

原文链接: https://pinguinorodriguez.cl/blog/self-unwrapping-routine/

comments powered by Disqus