-
-
Save gfldex/22f9133dbdb83c21c8e4 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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