Updated Fri, 09 Sep 2022 11:30:12 GMT

What are .first and .return when applied to subs?

I found the operator andthen somewhere, and when I looked it up in I found this:

#!/bin/env raku
sub load-data
  rand  > .5 or return; # simulated load data failure; return Nil
  (rand > .3 ?? 'error' !! 'good data') xx 10 # our loaded data 
load-data.first: /good/ andthen say "$_ is good";
# OUTPUT: (good data is good) 
load-data() andthen .return; # return loaded data, if it's defined 
die "Failed to load data!!";  

So, how do .first and .return work when applied to subs ? I've never seen such things before.


So, how do .first and .return work when applied to subs ?

It doesn't. You're not calling .first or .return on a Sub object.

When a subroutine in Raku appears with no arguments, it's called. So simply writing load-data on its own calls load-data, as though you'd put parentheses. (Note: If you want to get the subroutine object itself, you use an & sigil, so &load-data is the subroutine object) So this

load-data.first: /good/ andthen say "$_ is good";

is equivalent to something like this in a more familiar Python-like syntax.

load_data().first("good") and say(...)

First, load-data is called. It returns either Nil or a list of data. Now first is called on the list.

Returns the first item of the list which smartmatches against $matcher, returns Nil when no values match. The optional named parameter :end indicates that the search should be from the end of the list, rather than from the start.

You've called it as load-data.first: /good/, so it will return the first element of the list for which the regular expression /good/ matches, or Nil otherwise.

andthen is like &&, except that while && checks for truthiness (via a Boolean context), andthen checks for definedness via the multi method defined. Objects like Nil and failures are undefined, while most "ordinary" objects like numbers and strings are defined. In particular, things that are conventionally "falsy" like zero or the empty string are still defined. Like &&, andthen short-circuits, so if the left-hand side is undefined, then the right-hand side never evaluates.

Finally, there's one other quirk that enables this line to work. andthen is designed to work with this exact use case: Do thing 1 and, if that thing succeeds, do thing 2. Frequently, thing 2 depends on the result of thing 1. So andthen assigns the result of the left-hand side to the special variable $_ during evaluation of the right-hand side. That's a mouthful, so let's write it out. x andthen y does the following.

  1. Evaluate x.
  2. If x is not defined, then stop. We're done. Return x.
  3. Otherwise, set $_ to the result of x.
  4. Now evaluate y (with $_ set) and return it.

So, taking it all in...

load-data.first: /good/ andthen say "$_ is good";

Load the data, find the first element of the loaded data which matches a regex, and if one exists, then print it out.

Now let's take a look at your second example. There's another fun little piece of syntax sugar you need to know about for this one. In general, a dot at the beginning of a statement carries an implied $_. So .say is equivalent to $_.say, which is equivalent to say($_:), which evaluates after method resolution to say($_).

And believe it or not, return is a subroutine. Seriously. It's not a keyword; it's a sub. Go evaluate &return.WHAT in your REPL right now. It'll say (Sub).

load-data() andthen .return;
die "Failed to load data!!";

load-data gets called, and if it returns anything defined, that defined value gets assigned to $_. Then .return happens, which is equivalent to $_.return which (eventually) becomes return($_). If the value was defined, it gets returned from the function and the second line never runs. If not, we die with an appropriate error message.

Note that the parentheses are actually required in this case. If we write

load-data andthen .return;

Then Raku tries to interpret andthen .return as an argument to load-data and complains that no subroutine called andthen exists. It thinks we meant


In summary, Raku is a big bag full of syntax sugar wrapped in a parser from the sixth circle of Hell that's whitespace- and context- sensitive, and the result is a genuinely beautiful language.