如何解决Raku 中函数对象“power”运算符的实现 测试奖金乐/APL信息:TL;DR重复组合身份优先级签名
在 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 Functional Symbol 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's answer 通过以下所有测试。
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])';
is plus1(3,[1,16,'plus1(3,True),True)';
is (&plus1 ⍣ 0)(4),4,'(&plus1 ⍣ 0)(4)';
is (&plus1 ⍣ 10)(4),14,'(&plus1 ⍣ 10)(4)';
is (&plus1 ⍣ 10)([1,22,'(&plus1 ⍣ 10)([1,3])';
is (&plus1 ⍣ 3)(4,8,'(&plus1 ⍣ 3)(4,True)';
is (&plus1 ⍣ 3)(3,18,'(&plus1 ⍣ 3)(3,3])';
is (&plus1 ⍣ 3)({a => 1,b => 3,c => 5}),12,'(&plus1 ⍣ 3)({a => 1,c => 5})';
done-testing;
解决方法
- 应该如何扩展或用更好的实现替换它 适用于多个(或任何)签名?
Raku 实际上有一个与 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
如果您想要更详细(支持一元函数和二元函数,并接受一个函数作为您提到的第二个操作数 way APL does),那么您可以将其扩展为这样的:
#| 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
precedence traits 指定优先级。我不确定您想要的优先级到底有多高,但您基本上可以随心所欲地获得它。
- 是否有更好的方法来定义 f ⍣ 0 的“恒等函数”结果?
通过将 ⍣
定义为减少,这自然会发生。
奖金乐/APL信息:
这不是回答您的问题所必需的,但如果您对 Raku 和 APL 都感兴趣,这似乎是您可能想知道的:所有 Raku 函数都可以在 infix form 中调用,这使得它们非常类似于 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)};
,
TL;DR
(太长;没读)
这非常有效。
sub infix:<⍣> ( &func,UInt $repeat ) { [∘] &func xx $repeat }
重复组合
在数学中,乘法 ×
很像重复加法 +
。
2 × 5 = 2 + 2 + 2 + 2 + 2
还有其他方法可以在 Raku 中编写。
[+] 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
身份
我没有专门处理 $repeat
为 0
的情况。
尽管如此,它仍然做了一些明智的事情。
say (&plus1 ⍣ 0)(5); # 5
那是因为 [∘]()
的无参数形式只返回一个函数,该函数只返回其输入不变。
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
将其设置为更严格的优先级。
(在这种情况下更有意义,因为×
是tighter
而不是+
,所以⍣
也应该比∘
更紧。)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
如果你给它多个字幕,它也会正常工作。
所以总的来说,仅使用 [∘]
和 xx
效果非常好。
sub infix:<⍣> ( &func,UInt $repeat ) {
[∘] &func xx $repeat
}
say (&plus1 ⍣ 3)(4);
实际上,您的操作员不会使此类代码比我们用来实现您的操作员的代码短得多。 (仅缩短了 4 个字符。)
say [∘](&plus1 xx 3)(4);
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。