Apropos proto: Perl6.c multi thoughts

Multi routines are pretty neat, but seem incomplete to me. Some background- one can compute factorials this way:


multi fac(0) { 1 }
multi fac(Int $n where 1..Inf) { $n * fac( $n-1 ) }
say fac(4); # 24

Now what if we want to pass our recursive-multi-sub "fac" as a callback?

given &fac -> $some_fun { say "some_fun(4)=",$some_fun(4) }

Now... what about defining an anonymous multi-sub?

my $anon_fac = do {
    multi hidden_fac(0) { 1 }
    multi hidden_fac(Int $n where 1..Inf) { $n * fac( $n-1 ) }
    &hidden_fac };
say $anon_fac(4); # 24

It works, but it's a bit of a hack, and our multi-sub not truly anonymous. It's merely hidden. A truly anonymous object is not installed in any scope, whereas in this example, "hidden_fac" is installed in the lexical scope of the "do" block.

The Perl6 specs don't rule out anonymous multi routines, and in fact a Rakudo error message hints that one implementer along the way considered a way to do it:

my $anon_fac = anon multi sub(0) { 1 }

Gives error "Cannot use 'anon' with individual multi candidates. Please declare an anon-scoped proto instead"- ah, makes sense, here's why.

Let's rewind to the original example that starts with "multi fac(0) { 1 }". When the compiler sees that, it creates a "proto fac" for us in the same scope as the multi definition. A proto acts as a dispatcher- conceptually, when we call fac(4) we're asking the proto fac to pick one of the multi facs for us.

We can explicitly define a proto ahead of time, and even improve on the permissive default "proto" by specifying that all its routines will require Int.


proto fac_with_proto(Int) { * }
multi fac_with_proto(0) { 1 }
multi fac_with_proto(Int $n where 1..Inf) { $n * fac( $n-1 ) }
say fac_with_proto(4); # 24

Thus, the error that anon multi sub gives- Please declare an anon-scoped proto instead- is telling us "without a scope to install into, I can't vivify a proto for you. Make your own anon proto, and then attach this routine to it."
Yes Camelia, thanks for the tip! Trying it out...

my $fac_proto = anon proto uninstalled-fac(Int) { * };
say $fac_proto.name; # uninstalled-fac

Great! Now all we need to do is add the multis to that proto.

Aye, there's the rub. $fac_proto is a Sub object, which has methods to tell you the candidates, but no way to set the candidates. And I can't find any way to pass in a list of candidates at creation.

Apropos a proper proto patch

What would make proto/multis clean and orthoginal would be a way to


  • specify candidates at compile time
  • add candidates at run time

Something like

my $future_fac = Proto( :dispatch(sub (Int) {*}),
:candidates([sub (0) {1}]),
:mutable );
$future_fac.candidates.push(
sub (Int $n where 1..Inf) { $n * fac( $n-1 ) }
);
$future_fac(4); # 24

I imagine a Proto subclass of Sub to expose multi-routines' inner workings. The constructor would allow defining anything the proto declaration does: signature & default routine and name. Plus, it would have an attribute allowing passing in an initial candidate list.

Finally, the object itself would make the candidates method return an array, instead of an immutable list, if the Proto was created with a mutable attribute. Not specifying mutable would mean all multi's need to be added at compile-time, and none allowed at run-time.

Now, I have never read Rakudo source, and my total exposure to multi routines has been reading the specs in the wake of a couple perl6-compiler threads half a year ago. So take this with a grain of salt and without a proof-of-concept, alas. Still... what do you think? Does it fit in with your view of the grand scheme of multi-things, dear reader?

2 Comments

I agree that being able to compose dispatchers and candidates procedurally is very useful. And you can sorta do it. Check out:

https://github.com/LLFourn/p6-CompUnit-Util#dispatcher-manipulation

especially this line: https://github.com/LLFourn/p6-CompUnit-Util/blob/master/lib/CompUnit/Util.pm6#L233 where I have to create a proto. This works but there is a serialization bug which breaks precompilation if it's done at compile time.

but doing it at runtime should work:


sub create-proto($name) {
my $proto = (my proto anon (|) {*}).clone;
$proto.set_name($name);
return $proto;
}

my $proto = create-proto('foo');
$proto.add_dispatchee(anon sub candidate1('one') { 'one' });
$proto.add_dispatchee(anon sub candidate2('two') { 'two' });

say $proto.('one');
say $proto.perl;
.say for $proto.candidates;

The above will work but it's quite fragile. Try assigning the sub to a variable first and then .add_dispatchee (it will die). It definitely needs a nicer interface. The design docs say that protos should have .push and mentions that you should be able to do Proto.new. https://design.perl6.org/S06.html#Introspection

Leave a comment

About Yary

user-pic Programming for decades, and learning something new every day.