Day 13 – A new trait for old methods

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 = $;

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: $;

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.
            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., valid => True, :invalid, :also-invalid).say;


<<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!