Raku is full of neat little tricks and gems that can make the programmer’s life simpler (or at least a whole more entertaining). And as a language that rewards mastery, it’s not a surprise that these can combine into sophisticated solutions to common problems.

This post will illustrate this point with one such example: sharing parameters in a command line application. But first, we’ll need to cover some of the neat little building blocks that allow for this.

Command line interfaces

Raku has built-in support for writing command line interfaces. If a file has a MAIN subroutine, it will called automatically when the file is directly executed. And more interestingly will use that subroutine’s signature to automatically parse the command line arguments.

That means that an executable file named cli that looks like this:

#!/usr/bin/env raku

sub MAIN ( Str $command, Str $path, Bool :$debug ) {
    note "Working on $path" if $debug;

    given $command {
        when 'grep' {
            .say for $path.IO.lines.grep: /raku/;
        }
        when 'count' {
            say "$_ has { .IO.lines.elems } lines" given $path;
        }
        default {
            say "Unknown command: $command";
            say $*USAGE;
        }
    }
}

will result in the following output:

$ ./cli grep ./cli
#!/usr/bin/env raku
            .say for $path.IO.lines.grep: /raku/;
$ ./cli count ./cli
/home/user/cli has 17 lines
$ ./cli
Usage:
  /home/user/cli [--debug] <command> <path>
$ ./cli reverse ./cli
Unknown command: reverse
Usage:
  /home/user/cli [--debug] <command> <path>

Multiple dispatch

This is made possible by the signature in the MAIN subroutine, which specifies what parameters that subroutine can take (and of course, which ones it cannot, like that --fake-option one I used).

But the compiler not only can determine whether a function call is valid or not based on its signature, it can also decide which function to call based the arguments used.

We can use this to expand our application:

#!/usr/bin/env raku

multi sub MAIN ( 'grep', Str $path, Bool :$debug ) {
    note "Working on $path" if $debug;
    .say for $path.IO.lines.grep: /raku/;
}

multi sub MAIN ( 'count', Str $path, Bool :$debug ) {
    note "Working on $path" if $debug;
    say "$_ has { .IO.lines.elems } lines" given $path;
}

multi sub MAIN (
    Str $command, Str $path, Bool :$debug,
) is hidden-from-USAGE {
    note "Working on $path" if $debug;
    say "Unknown command: $command";
    say $*USAGE;
}

which will keep the behaviour for all the above calls, but will now give us a different help message:1

$ ./cli --help
Usage:
  /home/user/cli [--debug] grep <path>
  /home/user/cli [--debug] count <path>

This new version keeps different behaviours separate, which makes implementing a new command a lot simpler. But it also has some unwanted issues, like forcing us to repeat the shared parameters on each entry, which can get pretty unwieldy with more complex applications.

Luckily, we have another ace up our sleeves.

Subroutine prototypes

When declaring multi subroutines we can declare common behaviours by declaring a proto. This can be useful when we want to make sure that we don’t accidentally make the $path into an Int, for example. But we can also use it for our application:2

#!/usr/bin/env raku

proto sub MAIN ( $, Str $path, Bool :$debug, | ) {
    note "Working on $path" if $debug;
    {*}
}

multi sub MAIN ( 'grep', Str $path, *% ) {
    .say for $path.IO.lines.grep: /raku/;
}

multi sub MAIN ( 'count', Str $path, *% ) {
    say "$_ has { .IO.lines.elems } lines" given $path;
}

multi sub MAIN ( Str $command, Str $path, *% ) is hidden-from-USAGE {
    say "Unknown command: $command";
    say $*USAGE;
}

The proto defines a common portion of MAIN in which we can place any shared behaviours (including for example any initialisation we may want to do with our now global parameters). Unfortunately, it does mean that our automatically generated usage message gets slightly less nice:3

$ ./cli --help
Usage:
  /home/user/cli grep <path>
  /home/user/cli count <path>

TIMTOWTDI

As always, there is more than one way to do it,4 and it’s up to you as the developer to decide which one is the most appropriate for your needs.

I’m just glad that Raku has all these tools at my disposal, and gives me enough rope room to grow.

  1. Note how we can use the is-hidden-from-USAGE trait to tell Raku to ignore a particular subroutine when generating the usage message. This is another of those nice little tricks Raku is littered with. 

  2. The | at the end of the proto declaration declares a capture parameter, and in this case has the effect of saying that the specific implementations of this proto may have other parameters (like sub-command-specific options, for example).

    The *% I used in the MAIN subs is a slurpy parameter which slurps in any non-specified parameters to lets that signature match even if I don’t bother to specify a :$debug named parameter, for example. I could have used a | instead, but that has some effects on the usage message that I wanted to avoid. 

  3. Fortunately, there are ways in which we can solve this by either expanding the built-in message available in $*USAGE or replacing it entirely by defining our own USAGE subroutine. 

  4. You could also us unit to define file-wide MAINs for each separete command, and leave the top level file to dispatch to the right file!