Dissecting a Raku snippet
A couple of days ago, while prototyping the solution to a problem I had, I wrote a bit of code that brought immense joy to me. It was the kind of code that makes you giggle each time you see it run just because of how clever and elegant the solution seems, and because of the excitement of proving that, against your expectations, it actually works.
Wanting to share the excitement, I shared it online. The snippet looked like this:
sub foo ( $a, $b ) { $a ~ $b }
my $wh;
$wh = &foo.wrap: {
LEAVE &foo.unwrap: $wh;
callwith( $^b, $^a );
}
say foo( 1, 2 ) for 1 .. 3;
This is very dense.
And a bit cryptic.
And hella fun.
So let’s break it down. To do this, we’ll start from the end, and work our way back to the beginning.
The results
When the code above gets executed, it prints out the following lines:
21
12
12
This is the result of the final line:
say foo( 1, 2 ) for 1 .. 3;
which prints the result of calling foo( 1, 2 )
three times. This line will
be familiar to Perl programmers because Raku supports “statement modifiers”,
which it inherited from Perl: the for
at the end of the statement modifies
the statement that comes before, and executes it once for each element in the
1 .. 3
range (which is actually a Range object).
But if we are calling foo( 1, 2 )
three times, then why does it output 21
the first time we call it, and 12
the rest of the times?
Wrapping code
That’s because of the previous block:
my $wh;
$wh = &foo.wrap: {
LEAVE &foo.unwrap: $wh;
callwith( $^b, $^a );
}
This bit of code is where the core of the magic happens. ✨
The first bit that matters is the call to wrap
This is called on the
foo
subroutine object (which is why we reference it with the &
sigil,
otherwise we might call the function rather than get a reference to it).1
The wrap
method allows us to attach a block of code to foo
, so that
calling that function will execute the block of code instead.
Re-dispatch
Inside the block we are using to wrap foo
we use callwith
to be able
to call the wrapped subroutine. We could use a number of different
alternatives depending on whether we wanted to get a return value and whether
we wanted to modify the arguments the underlying function gets called with.
In this case, callwith
allows us to modify the arguments, but it never
returns. And we modify the arguments by calling it like callwith( $^b, $^a )
.
But where did these variables come from?
Self-declared positionals
They are self-declared positional arguments.
When a block does not define an explicit list of arguments, any variables
used within its scope that use the ^
twigil (= any variable with a ^
after
the sigil) declares an implicit positional argument. Each positional the block
receives will get assigned to these variables in alphabetical order.
In the example above, then, the call callwith( $^b, $^a )
will re-dispatch
to the function we are wrapping, with the first and second arguments switched.
Since the original subroutine concatenates both arguments (with the ~
operator), this explains the first line of output: we call foo( 1, 2 )
and
this re-dispatches to foo( 2, 1 )
which results in the 21
line.
Progress!
Now for the truly magic bit.
Self-unwrapping wrappers
Before actually re-dispatching to the original code, we have another line:
LEAVE &foo.unwrap: $wh;
This line uses a LEAVE
phaser to register a statement to be called when
execution leaves the current block. So after switching the arguments and
re-dispatching to the original subroutine, execution leaves the block and this
statement finally gets executed.
When it does, it calls the unwrap
method on foo
to remove the wrapping
code (it does this by passing in the wrapping handle that was stored in the
$wh
variable).
Wrap-up
And that’s it! The snippet defines a foo
subroutine that takes two arguments
and concatenates them. It then wraps it into a block of code that swaps the
arguments before calling the original subroutine, and then removes itself as
if it was never there.
This explains the output: the first time we call foo( 1, 2 )
the arguments
get swapped and we print 21
, but on subsequent calls there is no wrapping
code anymore, so we get lines like 12
.
What a mouthful! But all of that in seven lines of code. Raku can really pack a punch. ✊
That’s nice, but what’s the real use?
Incidentally, this was something that I was prototyping to solve a real problem: I had a callback that would get called repeatedly, and I wanted to hook into the first time that callback was executed so I could run a preliminary check. But once the check had passed, there was no point in doing it every time. Unwrap to the rescue.
-
Incidentally, this makes use of another useful Raku feature: in Raku everything is an object. This is what allows us to, for example, seamlessly call methods on subroutines. ↩