Create a gist now

Instantly share code, notes, and snippets.

@gfldex /strict-new.p6 Secret
Created Dec 6, 2015

What would you like to do?
use v6;
# El_Che expressed the desire on L<IRC http://irclog.perlgeek.de/perl6/2015-12-03#i_11651949> 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 (C<.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 arguments startes 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) {
# say 'wrapper ', args.perl, ' ', args.hash.keys.perl, ' ', @named-params.perl;
# We first check with the subset operator C<(<=)> if there are any named arguments we don't like and if so, we use the set difference operator C<(-)> 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 C<is strict> will learn about C<:$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!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment