use v6;
El_Che expressed the desire on IRC to have Perl 6 watch his fingers when typing named arguments. It is indeed quite easy to send a wrong name the right way. Luckily we can help him.
First we need some exception to throw. Perl 6 kindly creates a constructor for us that deals with filling all attributes with values, so we can provide details when we throw the exception.
class X::Parameter::ExtraNamed is Exception { has $.extra-parameters; has $.classname; has $.method-name; method message () { "The method $.method-name of $.classname is offended by the named parameter(s): $.extra-parameters"; } }
We want to modify a method (.new
in this case) so we need a trait. A trait is a modification of the Perl 6 grammar and a subroutine. It’s name comes from the required named argument. The thing it operates on is stored in the first positional. In our case that’s a method.
multi sub trait_mod:<is>(Method $m, :$strict!){
We want to check named arguments against the list of arguments that are defined in the methods signature. We have to store them somewhere.
my @named-params;
When we provide some error message we may want to name the class the method is member of.
my $invocant-type; my $method-name = $m.name;
The signature of a method can provide us with a list of parameters.
for $m.signature.params -> $p {
But first the classname.
$invocant-type = $p.type if $p.invocant;
We are only interested in named arguments.
next unless $p.named;
Each named argument starts with a sigil ($ in this case). That would get in our way, so we strip it from the method name and store the rest for safekeeping.
@named-params.push: $p.name.substr(1); }
After we got all the information we need, we come to the business end of our trait. We wrap the method in a new method. We only want to peek into it’s argument list so we take a capture. Conveniently a capture provided us with a method to get a list of all named arguments it contains.
$m.wrap: method (|args) {
We first check with the subset operator (<=)
if there are any named arguments we don’t like and if so, we use the set difference operator (-)
to name only those. The exception we defined earlier takes care of the rest.
X::Parameter::ExtraNamed.new( method-name => $method-name, classname => $invocant-type.perl, extra-parameters => args.hash.keys (-) @named-params ).throw unless args.hash.keys (<=) @named-params;
If all named arguments are fine, we can call the wrapped method and forward all arguments kept in the capture.
callwith(self, |args); } }
Let’s test it.
class Foo { has $.valid;
Perl 6 will deal with the positional part, our trait is strict
will learn about :$valid
.
method new (Int $p1, :$valid) is strict { self.bless(valid => $valid) } method say () { say 'Foo' } }
New instance is new and got 2 extra named arguments the method doesn’t know of.
Foo.new(42, valid => True, :invalid, :also-invalid).say;
OUTPUT:
<<The method new of Foo is offended by the named parameter(s): invalid also-invalid>>
Traits are a nice way to generalise behaviour of methods and subroutines. Combined with exceptionally good introspection Perl 6 can be mended and bended to fit into any Christmas package. Merry 1.0 and a happy new Perl 6 to you all!