Computer Things

Subscribe
Archives
October 26, 2023

Raku is surprisingly good for CLIs

A while back I wrote Raku: a Language for Gremlins about my first experiences with the language. After three more months of using it I've found that it's quite nice for writing CLIs! This is because of a couple features:

MAIN params become options

Like many other programming languages, Raku has a special-cased MAIN function that acts as the entry point into your script. But unlike f.ex C, MAIN can have an arbitrary signatures:

# strcmp.raku

sub MAIN($str1, $str2) {
  ... # blah blah blah
}

Then when you call the file:

> raku .\strcmp.raku
Usage:
  .\strcmp.raku <str1> <str2>

Raku automatically converts MAIN's parameters into script arguments! That's already pretty useful, and it gets better:

sub MAIN(
  $str1,         #= first string
  $str2 = "foo", #= second string
) {
  ... 
}
> raku .\strcmp.raku
Usage:
  .\strcmp.raku <str1> [<str2>]

    <str1>      first string
    [<str2>]    second string [default: 'foo']

So now we also have help messages and default values. To get flags and options, just define keyword parameters:

sub MAIN(
  $str1, #= first string
  $str2, #= second string
  Bool :$i, #= case insensitive?
  Int :$option #= some integer option
) {
  ...
}
Usage:
  .\strcmp.raku [-i] [--option[=Int]] <str1> <str2>

    <str1>            first string
    <str2>            second string
    -i                case insensitive?
    --option[=Int]    some integer option

Here's what the same CLI would look like in Python:

import argparse

if __name__ == "__main__":
    parser = argparse.ArgumentParser()

    parser.add_argument("str1", help="first string", type=str)
    parser.add_argument("str2", help="second string", type=str)
    parser.add_argument("-i", help="case insensitive?", action="store_true")
    parser.add_argument("--option", help="some integer option", type=int)

    args = parser.parse_args()

That's over twice as long to do the same thing— three times as long without the help strings. That said, there's a few special things I do with argparse that don't seem possible in Raku. But I still prefer the latter approach, especially when we throw in the other big feature:

Multimethods make subcommands easier

Raku has multiple dispatch: you can define multiple functions with the same name but different parameters, and then when you call the function it will pick the "most appropriate" method based on the supplied arguments.

# Instead of

sub myroot($val, $root = 2) { ... }

# You can write

multi myroot($val, $root) { ... }
multi myroot($val) { myroot($val, 2) }

This is already useful for defining parameter groups, like saying MAIN can accept either the --foo option or the --bar option but not both. But multimethods can also dispatch on values, like exact strings. That gives you subcommands!

multi MAIN( 
  "profile",
  $file,
  Bool :$verbose,
  :$output = $*OUT
) {
  ... 
}

multi MAIN( 
  "reformat",
  $file,
  Bool :$dry-run = False
) {
  ...
}
Usage:
  .\strcmp.raku [--verbose] [--output[=Any]] profile <file>
  .\strcmp.raku [--dry-run] reformat <file>

I use this to group a bunch of thematically-similar tools in the same file and distinguish calling them with the subcommands.

Miscellaneous useful things

There's a few small things that aren't as important as those two features, but still help with QoL.

  1. You can give flags "aliases" by writing Bool :v($verbose). Then you can call it on the CLI as either -v or --verbose.
  2. You can define the special hash %*SUB-MAIN-OPTS to configure how the arguments are parsed. F.ex if you add the :bundling key, then it will accept -abc in addition to -a -b -c.
  3. Raku provides some hooks to override CLI behavior, like if you define the ARGS-TO-CAPTURE function you can preprocess arguments before dispatching to MAIN.

One thing I've not be able to figure out is how to make variadic options, like having --src and --dest both take multiple paths. AFAICT the best you can do is make them both take flat strings and then postprocess them into paths. Maybe that'll get proper support in the future!

If you're reading this on the web, you can subscribe here. Updates are once a week. My main website is here.

My new book, Logic for Programmers, is now in early access! Get it here.

Don't miss what's next. Subscribe to Computer Things:
Start the conversation:
This email brought to you by Buttondown, the easiest way to start and grow your newsletter.