Raku中函数对象“power”运算符的实现
在 APL 中有幂运算符?,如果将其应用于函数,则会f叠加f. 如何在 Raku 中实现该运算符?
例如,使用以下定义f:
sub f(Int:D $i){ $i + 1 }
该命令say (f ? 4)(10);应等效于say f(f(f(f(10))));.
我在下面的实现是针对一个带有一个参数的函数。
问题
-
如何使用适用于多个(或任何)签名的更好的实现来扩展或替换它?
-
如何定义该新电力运营商的“高优先级”?
-
有没有更好的方法来定义“身份函数”结果
f ? 0?
参考链接
这是 APL 的描述?:
“Power Operator”。
(?是一个“带两个点的星星”,或者更正式的“Apl 功能符号 Star Diaeresis”。)
试图
这是一个实现的尝试:
sub infix:<?>( &func, Int:D $times where $times >= 0 ) {
if $times == 0 {
sub func2($d) {$d}
} else {
sub func2($d) {
my $res = &func($d);
for 2..$times -> $t { $res = &func($res) }
$res
}
}
}
sub plus1(Int:D $i){ $i + 1 }
say 'Using (&plus1 ? 0) over 4 :';
say (&plus1 ? 0)(4);
say 'Using (&plus1 ? 10) over 4 :';
say (&plus1 ? 10)(4);
# Using (&plus1 ? 0) over 4 :
# 4
# Using (&plus1 ? 10) over 4 :
# 14
(我跟着这个教程页面:https : //docs.raku.org/language/optut。)
测试
Brad Gilbert 的答案提供的定义通过
了以下所有测试。
use Test;
sub infix:<?> ( &func, UInt $repeat ) is tighter( &[?] ) { [?] &func xx $repeat }
proto plus1(|) {*}
multi plus1(Int:D $i){ $i + 1 }
multi plus1(Bool:D $b){ $b.Int + 1 }
multi plus1(Int:D $i, Bool:D $b){ $i + $b.Int + 1 }
multi plus1(Int:D $i, Int:D $j){ $i + $j + 1 }
multi plus1(@j){ ([+] @j) + 1}
multi plus1(Int:D $i, @j){ plus1($i) + plus1(@j) - 1 }
multi plus1(%h){ ([+] %h.values) + 1 }
plan 9;
is plus1([1, 3, 5, 3]), 13, 'plus1([1, 3, 5, 3])';
is plus1(3, [1, 3, 5, 3]), 16, 'plus1(3, [1, 3, 5, 3])';
is plus1(3, True), 5, 'plus1(3, True)';
is (&plus1 ? 0)(4), 4, '(&plus1 ? 0)(4)';
is (&plus1 ? 10)(4), 14, '(&plus1 ? 10)(4)';
is (&plus1 ? 10)([1, 3, 5, 3]), 22, '(&plus1 ? 10)([1, 3, 5, 3])';
is (&plus1 ? 3)(4, True), 8, '(&plus1 ? 3)(4, True)';
is (&plus1 ? 3)(3, [1, 3, 5, 3]), 18, '(&plus1 ? 3)(3, [1, 3, 5, 3])';
is (&plus1 ? 3)({a => 1, b => 3, c => 5}), 12, '(&plus1 ? 3)({a => 1, b => 3, c => 5})';
done-testing;
回答
- 如何使用适用于多个(或任何)签名的更好的实现来扩展或替换它?
乐居然有一个运营商,这不是太远离及APL的?建于:xx,列表中的重复操作。(毕竟,函数列表仍然是列表!)。使用xx,我们可以?在一行中定义:
sub infix:<?>(&fn, $count) { &{($^?, |(&fn xx $count)).reduce({$^b($^a)})} };
然后像在您的&plus1示例中一样使用它:
sub plus1(Int:D $i){ $i + 1 }
say 'Using (&plus1 ? 0) over 4 :';
say (&plus1 ? 0)(4);
say 'Using (&plus1 ? 10) over 4 :';
say (&plus1 ? 10)(4);
# Using (&plus1 ? 0) over 4 :
# 4
# Using (&plus1 ? 10) over 4 :
# 14
如果你想更精细(支持一元函数和二元函数,并像你提到的APL 那样接受一个函数作为你的第二个操作数),那么你可以将其扩展为这样的:
#| Monadic repetition
multi infix:<?>(&fn:($), $count) {
&{($^?, |(&fn xx $count)).reduce({$^b($^a)})} };
#| Dyadic repetition
multi infix:<?>(&fn, $i) {
sub (|c){(|c[0], |(&fn xx $i)).reduce: -> $sum, &f { f $sum, |c[*-1]}}};
#| "until" function operand
multi infix:<?>(&fn1, &fn2) {
sub ($a, $b) { given fn1($a, $b) { when .&fn2($a) { $a }
default { .&?ROUTINE($b) }}}}
sub plus1(Int:D $i, Bool:D $b) { $i + $b.Int + 1 }
say (&plus1 ? 6)(1, True); # OUTPUT: «13»
say (&{$^b + 1 ÷ $^a} ? &infix:<?>)(1, 1); # OUTPUT: «1.618033989»
# equivalent to 1+?÷?=1 ? fixpoint: golden mean.
(请注意,您可以扩展第二个 multi 以采用元数大于 2 的函数,但您需要决定在这些情况下您想要什么语义。另请注意,Raku 有一个&infix:<?>运算符,但它并没有扮演相同的角色作为 APL 的,因为它不传递调用参数。)
唯一不能让我们从 Dyalog 的幂运算符中获得的是能够提供负整数操作数来应用函数操作数的倒数。我们可以编写那个 multi,但是 Raku(就像几乎所有非 APL 语言一样)几乎没有系统地创建反函数,所以它似乎不太值得。
- 如何定义该新电力运营商的“高优先级”?
您可以指定它们的优先级is tighter/ is equiv/is looser 优先级特性。我不确定您想要的优先级到底有多高,但您基本上可以根据需要获得它。
- 有没有更好的方法来定义 f 的“身份函数”结果?0?
这通过定义?为减少自然而然地消失了。
奖金乐/APL信息:
这不是回答您的问题所必需的,但如果您对 Raku 和 APL 都感兴趣,这似乎是您可能想知道的:所有 Raku 函数都可以以中缀形式调用,这使得它们与 APL 二元函数非常相似。所以我们可以这样写:
my &f = &plus1 ? 6;
say 1 [&f] True;
与 相同的效果say (&plus1 ? 6)(1, True);。
此外,如果我理解正确的话,最常使用 APL 幂运算符的方法之一是通过使用布尔操作数的“条件函数应用程序”。Raku 支持与&infix:<xx>列表重复操作符非常相似的习语。所以,你可能在 APL 中有这个:
SquareIfNegative ? { (*?2?(?<0)) ? }
在乐,那可能是
my &square-if-negative = &{ $_² xx ($_ < 0)};
- This is a very instructive answer. That said, the provided definition of `⍣` does not pass all tests in my (updated) post.
- @AntonAntonov sure can. I edited my answer with a dyadic function that I believe would work similarly to the APL version.
回答
TL; 博士
(太长;没读)
这非常有效。
sub infix:<?> ( &func, UInt $repeat ) { [?] &func xx $repeat }
重复组合
在数学中,乘法×很像重复加法+。
2 × 5 = 2 + 2 + 2 + 2 + 2
还有其他方法可以在 Raku 中编写它。
[+] 2,2,2,2,2
[+] 2 xx 5
Raku 也有一个函数组合器,很像函数加法?。
(ASCII 版本只是o.)
sub A (|) {…}
sub B (|) {…}
my var = …;
A( B( var )) eqv (&A ? &B).(var)
首先看看我们如何在您的代码中使用该运算符。
sub plus1(Int:D $i){ $i + 1 }
my &combined = &plus1 ? &plus1;
say combined 0; # 2
就像我们可以将乘法定义为实际上是重复加法一样。
{
sub infix:<×> ( $val, $repeat ) {
&CORE:infix:<×>(
[+]( $val xx $repeat.abs ), # <--
$repeat.sign
)
}
say 2 × 5;
}
我们可以为您的运算符做同样的事情,根据重复的函数组合来定义它。
sub infix:<?> ( &func, UInt $repeat ) {
[?] &func xx $repeat
}
sub plus1(Int:D $i){ $i + 1 }
say (&plus1 ? 1)(0); # 1
say (&plus1 ? 10)(0); # 10
身份
我没有专门处理 where $repeatis的情况0。
尽管如此,它仍然做了一些明智的事情。
say (&plus1 ? 0)(5); # 5
那是因为无参数形式的[?]()just 返回一个函数,该函数只返回其输入不变。
say (&plus1 ? 0)('foobar'); # foobar
say [?]().('foobar'); # foobar
基本上,身份结果已经按照您的意愿行事。
您可能想[?] …知道当参数为零时如何知道要返回什么。
say ( [?] ).(4); # 4
问题是它真的没有。
或者更确切地说[ ] …,没有,但是&infix:<?>有。
.say for &infix:<?>.candidates».signature;
# ()
# (&f)
# (&f, &g --> Block:D)
这与两者都返回合理的原因相同。
say [+] ; # 0
say [×] ; # 1
优先级
设置优先级的最佳方法是首先找出其他运算符具有类似的优先级。
我将?作为一个例子来定义它。
-
您可以将其设置为与 相同的优先级
is equiv。(还将关联性设置为相同。)sub sub infix:<?> ( &func, UInt $repeat ) is equiv( &infix:<?> ) {…}请注意,由于 Raku 有很多地方指的是现有的中缀运算符,因此引用它们的方法更短。
sub sub infix:<?> ( &func, UInt $repeat ) is equiv( &[?] ) {…} # ^--^ -
您可以将其设置为具有更宽松的优先级
is looser。sub sub infix:<?> ( &func, UInt $repeat ) is looser( &[?] ) {…} -
或者您可以将其设置为具有更严格的优先级
is tighter。
(在这种情况下更有意义,因为×istighterthan+,所以也应该?比 紧?。)sub sub infix:<?> ( &func, UInt $repeat ) is tighter( &[?] ) {…}
默认优先级与+运算符相同。
签名
至于签名,?只需将每个结果传递给下一个。
sub A ( a ) {
a + 1
}
sub B ( a, b ) {
a + b
}
say A(B(4, 5)); # 10
say (&A ? &B).(4, 5); # 10
假设A上面相反,期望两个值,并B提供两个值作为列表。
sub A ( a, b ) {
a + b
}
sub B ( a, b ) {
a + b, 1
}
然后这条线失败了。
它实际上甚至无法编译。
say A(B(4, 5));
这一行没有失败,实际上它返回了正确的值
say (&A ? &B).(4, 5); # 10
如果你给它多个 subs,它也能正常工作。
所以总的来说,只是使用[?]和xx工作得非常好。
sub infix:<?> ( &func, UInt $repeat ) {
[?] &func xx $repeat
}
say (&plus1 ? 3)(4);
实际上,您的运营商不会使此类代码比我们用来实现您的运营商的代码短得多。(仅短 4 个字符。)
say [?](&plus1 xx 3)(4);