【问题标题】:Implementation of a function object "power" operator in Raku在 Raku 中实现函数对象“power”运算符
【发布时间】:2021-07-24 13:35:30
【问题描述】:

在 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。)

测试

定义由 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, 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;

【问题讨论】:

  • 注意([+] @j)可以写成[+]( @j )](之间没有空格)。您也可以完全不使用括号 1 + [+] @j[+] 1, |@j

标签: raku infix-notation apl


【解决方案1】:
  1. 应如何扩展或替换为更好的实现 适用于多个(或任何)签名?

Raku 实际上有一个运算符,它与 APL 的 内置:xx(列表重复运算符)相差不远。 (毕竟,函数列表仍然是列表!)。使用xx,我们可以在一行中定义

sub infix:<⍣>(&fn, $count) { &{($^ω, |(&fn xx $count)).reduce({$^b($^a)})} };

然后像在 &amp;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 有一个 &amp;infix:&lt;∘&gt; 运算符,但它没有'不扮演与 APL 相同的角色,因为它不传递调用参数。)

唯一没有让我们了解 Dyalog 的幂运算符的是提供负整数操作数来应用函数操作数的逆运算的能力。我们可以写那个 multi,但是 Raku(和几乎所有的非 APL 语言一样)并没有系统地创建逆函数,所以它看起来不太值得。

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

您可以使用is tighter/is equiv/is looserprecedence traits 指定优先级。我不确定你到底想要多高的优先级,但你基本上可以得到你想要的任意高。

  1. 是否有更好的方法来定义 f ⍣ 0 的“恒等函数”结果?

通过将 定义为减少,这自然会失效。

Bonus Raku/APL 信息:

这不是回答您的问题所必需的,但如果您对 Raku 和 APL 都感兴趣,您可能想知道:所有 Raku 函数都可以在 infix form 中调用,这使得它们非常类似于APL 二元函数。所以我们可以这样写:

my &f = &plus1 ⍣ 6;
say 1 [&f] True;

效果和say (&amp;plus1 ⍣ 6)(1, True);一样。

另外,如果我理解正确,APL 幂运算符最常用的一种方式是使用布尔操作数与“条件函数应用程序”一起使用。 Raku 支持与&amp;infix:&lt;xx&gt; 列表重复运算符非常相似的习语。所以,你可能在 APL 中有这个:

SquareIfNegative ← { (*∘2⍣(⍵<0)) ⍵ }

在 Raku 中,可能是

my &square-if-negative = &{ $_² xx ($_ < 0)};

【讨论】:

  • 这是一个简洁优雅的实现——谢谢!可以修改它以处理具有多个参数的函数吗?例如。 plus1(Int:D $i, Bool:D $b){ $i + $b.Int + 1 }.
  • @AntonAntonov 当然可以。我用一个二元函数编辑了我的答案,我相信它的工作原理与 APL 版本类似。
  • 这是一个非常有启发性的答案。也就是说, 提供的定义没有通过我(更新的)帖子中的所有测试。
【解决方案2】:

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

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

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

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

基本上,身份结果已经做了你想要的。


您可能想知道[∘] … 如何知道在参数为零时返回什么。

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

事实是它真的没有。
或者更确切地说,[ ] … 没有,但 &amp;infix:&lt;∘&gt; 有。

.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

如果你给它多个 subs,它也可以正常工作

所以总的来说,仅使用 [∘]xx 就非常有效。

sub infix:<⍣> ( &func, UInt $repeat ) {
  [∘] &func xx $repeat
}

say (&plus1 ⍣ 3)(4);

真的,您的操作员不会使此类代码比我们用来实现您的操作员的代码短得多。 (仅短 4 个字符。)

say [∘](&plus1 xx 3)(4);

【讨论】:

  • 这太好了,谢谢!您的实现通过了我为 ⍣ 所做的所有测试。 (请参阅我更新的问题。)
猜你喜欢
  • 1970-01-01
  • 2015-09-20
  • 1970-01-01
  • 2021-10-31
  • 2020-12-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多