Raku 中函数对象 "power" 运算符的实现

Implementation of a function object "power" operator in Raku

在 APL 中有幂运算符 ,如果将其应用于函数 f,则会叠加 f 的应用。如何在 Raku 中实现该运算符?

例如,使用 f 的定义:

sub f(Int:D $i){ $i + 1 }

命令 say (f ⍣ 4)(10); 应该等同于 say f(f(f(f(10))));.

我下面的实现是针对具有一个参数的函数。

问题

  1. 如何使用适用于多个(或任何)签名的更好实现来扩展或替换它?

  2. 如何定义新的幂运算符的“高优先级”?

  3. 是否有更好的方法来定义 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。)

测试

定义由 通过以下所有测试。

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;
  1. How one should extend or replace it with a better implementation that works on multiple (or any) signatures?

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 语言一样)几乎没有系统地创建反函数,所以它看起来不太值得。

  1. How to define "high precedence" of that new power operator?

您可以使用 is tighter/is equiv/is looser precedence traits 指定优先级。我不确定您想要多高的优先级,但基本上您可以根据需要获得多高的优先级。

  1. Is there are better way to define the "identity function" result for f ⍣ 0?

通过将 定义为缩减,这自然会出现。

奖金Raku/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)) ⍵ }

在 Raku,那可能是

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,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

身份

我没有特别注意 $repeat0 的情况。
尽管如此,它仍然做了一些明智的事情。

say (&plus1 ⍣ 0)(5); # 5

这是因为 [∘]() 的无参数形式只是 return 一个函数,只是 return 它的输入不变。

say (&plus1 ⍣ 0)('foobar'); # foobar

say [∘]().('foobar'); # foobar

基本上,身份结果已经按照您的意愿进行了。


您可能想知道当参数为零时 [∘] … 如何知道要 return 什么。

say ( [∘]      ).(4); # 4

问题是它真的没有。
或者更确切地说 [ ] … 没有,但是 &infix:<∘> 有。

.say for &infix:<∘>.candidates».signature;
# ()
# (&f)
# (&f, &g --> Block:D)

这两个原因是一样的 return 一些明智的事情。

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));

这一行没有失败,事实上它return是正确的值

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);