My assumption in this question is that a signature that I specify completely describes everything that a method will accept. Apparently I'm wrong about that, but I'd like to get that anyway. If I don't specify it in the signature, I don't want other people to be able to provide it as an argument thinking it will do something.
I create a signature with a single positional parameter for a method, and I expect it to only accepta single positional argument. However, it also accepts named parameters without complaint:
class SomeClass {
method something ( Int $n ) { 'Ran something' }
}
put SomeClass.something: 137; # Ran something
put SomeClass.something: 137, :foo('bar'); # Ran something
But, if I define a method that takes a named parameter, that's called each time I define it second. Also, although I think I've said it requires a named paramter foo
, it's not required and it still accepts named parameters that I did not specify:
class SomeClass {
multi method something ( Int $n ) { 'Ran something' }
multi method something ( Int $n, :$foo ) { "Ran $foo" }
}
put SomeClass.something: 137; # Ran
put SomeClass.something: 137, :foo('bar'); # Ran bar
put SomeClass.something: 137, :bar('foo'); # Ran
So, some questions:
How do I specify a signature that encompasses everything I want to accept and excludes everything else?
How do I force Perl 6 to choose the closest matching signature?
In what order does Perl 6 decide to check the methods?
You're touching on one of the things that is hardest to grasp about multi-method dispatch. Most important thing to realize is that every method
signature has an implicit *%_
(aka a slurpy hash) if there isn't one specified. This means that it will eat any non-specific named parameters.
class A {
method a() { dd %_ } # %_ exists even if not specifically specified
}
A.a(:foo) # {:foo}
Second thing you need to realize, is that named parameters only act as tie-breakers. So if there are multiple candidates that have the same matching set of positional parameters, MMD will use the first one that works (taking into account that all unexpected named parameters are slurped by %_
):
class A {
multi method a(:$foo) { say "foo" }
multi method a(:$bar) { say "bar" }
}
A.a(:bar) # foo
This seemingly unexpected result is caused by the fact that:
:$foo
is optional:bar
is eaten by the implicit *%_
To make it work more like you expect, you need to put the candidates that need to be tie-broken into the order you would like them to fire, and make any named parameters mandatory:
class A {
multi method a(:$foo!) { say "foo" }
multi method a(:$bar!) { say "bar" }
}
A.a(:bar) # bar
If you have candidates taking multiple named parameters, it quickly becomes rather complex and you're possibly better off by just having a method that uses introspection of %_
:
class A {
method a() {
if %_<foo> {
say "foo"
}
elsif %_<bar> {
say "bar"
}
else {
die "None of the signatures matched"
}
}
}
A.a(:bar) # bar
Hope this makes things clearer :-)
Methods always come with an implicit *%_
parameter, cf
say method {}.signature #=> (Mu $: *%_)
This is by design, with the idea that subclasses may pick out the arguments they like and then redispatch via nextsame
et al (cf the design docs - not sure where or even if it's documented elsewhere).
There are several ways to reject undeclared named arguments, eg via a where clause
method m($positional, :$explicit, *% where !*) { ... }
or via an empty subsignature
method m($positional, :$explicit, *% ()) { ... }
When passing a non-declared argument, the former will fail with
Constraint type check failed in binding to parameter '<anon>'
and the latter with
Unexpected named argument '...' passed in sub-signature
I'll leave the exact semantics of multimethod dispatch for others to answer, but my heuristic rule of thumb is to make sure required named parameters are declared as such (ie the !
is present) and to put the more general methods last.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With