In APL there is the power operator ⍣
, which if applied to a function f
superimposes the application of f
. How to implement that operator in Raku?
For example, with this definition of f
:
sub f(Int:D $i){ $i + 1 }
the command say (f ⍣ 4)(10);
should be equivalent to say f(f(f(f(10))));
.
My implementation below is for a function with one argument.
How one should extend or replace it with a better implementation that works on multiple (or any) signatures?
How to define "high precedence" of that new power operator?
Is there are better way to define the "identity function" result for f ⍣ 0
?
Here is a description of APL's ⍣
:
"Power Operator".
(⍣
is a "star with two dots", or more formally "Apl Functional Symbol Star Diaeresis".)
Here is an attempt of an implementation:
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
(I followed this tutorial page: https://docs.raku.org/language/optut .)
The definition provided by Brad Gilbert's answer passes all of the tests below.
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;
(Too Long; Didn't Read)
This works fantastically.
sub infix:<⍣> ( &func, UInt $repeat ) { [∘] &func xx $repeat }
In math, multiplication ×
is a lot like repeated addition +
.
2 × 5 = 2 + 2 + 2 + 2 + 2
There are other ways to write that in Raku.
[+] 2,2,2,2,2
[+] 2 xx 5
Raku also has a function combinator that is a lot like function addition ∘
.
(ASCII version is just o
.)
sub A (|) {…}
sub B (|) {…}
my \var = …;
A( B( var )) eqv (&A ∘ &B).(var)
Here's a first look at how we could use that operator with your code.
sub plus1(Int:D $i){ $i + 1 }
my &combined = &plus1 ∘ &plus1;
say combined 0; # 2
Just like we could define multiplication as actually being repeated addition.
{
sub infix:<×> ( $val, $repeat ) {
&CORE:infix:<×>(
[+]( $val xx $repeat.abs ), # <--
$repeat.sign
)
}
say 2 × 5;
}
We can do the same thing for your operator, define it in terms of repeated function composition.
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
I didn't specifically take care of the case where $repeat
is 0
.
It still does something sensible though.
say (&plus1 ⍣ 0)(5); # 5
That is because the no argument form of [∘]()
just returns a function that just returns its inputs unchanged.
say (&plus1 ⍣ 0)('foobar'); # foobar
say [∘]().('foobar'); # foobar
Basically, the identity result already does what you want it to.
You may be wondering how [∘] …
knows what to return when there are zero arguments.
say ( [∘] ).(4); # 4
The thing is that it really doesn't.
Or rather [ ] …
doesn't, but &infix:<∘>
does.
.say for &infix:<∘>.candidates».signature;
# ()
# (&f)
# (&f, &g --> Block:D)
It's the same reason both of these return something sensible.
say [+] ; # 0
say [×] ; # 1
The best way you would set the precedence level is to first figure out what other operator has a similar precedence level.
I'm going to define it in terms of ∘
as an example.
You can set it to the same precedence level with is equiv
. (Also sets the associativity to be the same.)
sub sub infix:<⍣> ( &func, UInt $repeat ) is equiv( &infix:<∘> ) {…}
Note that since Raku has a lot of places where you are referring to an existing infix operator there is a shorter way to refer to them.
sub sub infix:<⍣> ( &func, UInt $repeat ) is equiv( &[∘] ) {…}
# ^--^
You can set it to have a looser precedence level with is looser
.
sub sub infix:<⍣> ( &func, UInt $repeat ) is looser( &[∘] ) {…}
Or you can set it to have a tighter precedence level with is tighter
.
(Makes more sense in this case since ×
is tighter
than +
, so too should ⍣
be tighter than ∘
.)
sub sub infix:<⍣> ( &func, UInt $repeat ) is tighter( &[∘] ) {…}
The default precedence level is the same as the +
operator.
As for the signatures, ∘
just passes each result on to the next.
sub A ( \a ) {
a + 1
}
sub B ( \a, \b ) {
a + b
}
say A(B(4, 5)); # 10
say (&A ∘ &B).(4, 5); # 10
Let's say that A
above instead expected two values, and B
provided two values as a list.
sub A ( \a, \b ) {
a + b
}
sub B ( \a, \b ) {
a + b, 1
}
Then this line fails.
It actually fails to even compile.
say A(B(4, 5));
This line does not fail, in fact it returns the correct value
say (&A ∘ &B).(4, 5); # 10
It would also just work if you gave it multi subs.
So then overall, just using [∘]
and xx
works fantastically.
sub infix:<⍣> ( &func, UInt $repeat ) {
[∘] &func xx $repeat
}
say (&plus1 ⍣ 3)(4);
Really your operator wouldn't be making such code much shorter than the code we used to implement your operator. (Only 4 characters shorter.)
say [∘](&plus1 xx 3)(4);