cod*_*ons 6 functional-programming rakudo purely-functional raku
下面的 &greet 函数是纯的,可以适当地标上is pure trait。
sub greet(Str:D $name) { say "Hello, $name" }
my $user = get-from-db('USER');
greet($user);
Run Code Online (Sandbox Code Playgroud)
然而,这不是:
sub greet {
my $name = get-from-db('USER');
say "Hello, $name"
}
greet($user);
Run Code Online (Sandbox Code Playgroud)
不过这个呢?
sub greet(Str:D $name = get-from-db('USER')) { say "Hello, $name" }
greet();
Run Code Online (Sandbox Code Playgroud)
从函数的“内部”来看,它看起来很纯粹——当参数绑定到相同的值时,它总是产生相同的输出,没有副作用。但是从函数的外部来看,它似乎是不纯的——当使用相同的参数调用两次时,它会产生不同的返回值。Raku/Rakudo 选择哪个前景?
在实现参数的默认值时,语言可能至少采用两种策略:
由于其动态特性,其中只有第二个对 Raku 真正有意义,这就是它所做的。
在执行策略 1 的语言中,将这样的函数标记为纯函数可以说是有意义的,因为计算每个调用点的默认寿命的代码,因此任何基于纯度的分析和可能的转换都必须处理评估默认值的代码,可以看到它不是纯值的来源。
在策略 2 和 Raku 下,我们应该将默认值理解为在其签名中具有默认值的块或例程的实现细节。因此,如果计算默认值的代码是不纯的,那么整个例程是不纯的,因此is puretrait 是不合适的。
更一般地,is pure如果对于给定的参数捕获,我们总是可以期望相同的返回值,则该特征适用。在给出的示例中,参数 capture\()与此相矛盾。
这里的另一种分解是使用multisubs 而不是参数默认值,并且只用is pure.
当您说 a sub 时is pure,您就保证任何给定的输入将始终产生相同的输出。在你的最后一个例子中sub greet,我认为你不能保证默认值的情况,因为数据库的内容可能会改变,或者get-from-db可能有副作用。
当然,如果你确定数据库没有变化,并且没有任何副作用,你仍然可以申请is puresub,但为什么要使用数据库呢?
你为什么要把一个子标记为is pure反正?好吧,它允许编译器在编译时对子例程的调用进行常量折叠。例如:
sub foo($a) is pure {
2 * $a
}
say foo(21); # 42
Run Code Online (Sandbox Code Playgroud)
如果您查看为此生成的代码:
$ raku --target=optimize -e 'sub foo($a) is pure { 2 * $a }; say foo(21)'
Run Code Online (Sandbox Code Playgroud)
那么你会在接近尾声时看到这个:
? ? - QAST::IVal(42)
Run Code Online (Sandbox Code Playgroud)
的42是恒定折叠呼吁foo(21)。所以这样整个调用都被优化掉了,因为 sub 被标记is pure并且你提供的参数是一个常量。