Home > Perl6 > I left my keys in a side-channel

I left my keys in a side-channel

While trying to bake option groups into a module I stumbled over a neat solution to a common problem. I wanted to have a subroutine as a where-clause that slurps up named arguments, does some checks and either returns True to satisfy the where-clause or to die with a proper error message. That was simple but incomplete. The where-clause doesn’t provide the name of the argument it is checking on, what would be needed to have a proper error message. Let’s have some (simplified) code.

sub checker(*%colon-keys){ sub ($value-from-where-clause) { True } }
sub f( *%h where checker(:a, :b, :c) ) {}

The sub checker is called before the where clause is actually checking anything. In Perl6 a where-clause is kind of syntaxy. If an expression is provided it will call that expression and keep it’s returned value to then go and do the actual check against the value of %h. This does not happen at compile time and the returned value is not cached. In our example an anonymous sub is returned that takes one argument (a requirement by where) and must return True to let the where-clause-check pass.

As you can see checker takes a list of colon-pairs. That leaves the question how we can provide additional information we might want to output in an exception, esp. if we want that parameter to be optional and avoid odd syntax. We could have an optional positional but then we can’t mix positional and named arguments in checker. Storing that value would be trivial because the anonymous sub that is returned could have a closure variable. We just need to fill it. Luckily the sub is returned as a reference that is then called by the where-clause. We can sneak another call in, as long as we we don’t forget to return the code reference to the anonymous sub.

Returning a reference to the same object is done by chainable method calls. Often, when things get complicated in Perl 6-land, we just mix a role in. Then we have a method and can return self.

use v6;

sub g($i){
    my $closure-variable;
    sub ($value-from-where-clause) {
        say [$closure-variable, $value-from-where-clause];
        $value-from-where-clause == $i
            or die "bad value $value-from-where-clause for $closure-variable"
    } but role :: {
        method side-channel($second-value){
            $closure-variable = $second-value;
            self
    }
  }
}

sub f($a where g(42).side-channel($a.VAR.name) ){ 'OK' }

say f(42);
# OUTPUT
# [$a 42]
# OK

And there we have it — a side-channel to a where-clause-call or any other spot we can use higher order functions at. Now I can go and provide MTA error messages for option groups.

Categories: Perl6