Home > Raku > Swarms of Exceptions

Swarms of Exceptions

According to Larry lazyness is a virtue. Let’s see if Rakudo agrees.

sub virtue { }; lazy virtue;

# OUTPUT: Nil

A few posts ago I lamented about the lack of Exceptions thrown by run, shell and qx{}. I since changed my mind. Neither of those constructs have any idea what OS they are running on or what a shell command even is. The programmer does (hubris).

This left me impatient. The compiler refuses to create an exception for me when Proc.exitcode is non-zero. In Raku that’s a solvable problem.

my $Exception = Metamodel::ClassHOW.new_type(:name($cmd-name));
$Exception.HOW.add_parent($Exception, Exception);
$Exception.HOW.add_attribute($Exception, Attribute.new(:name<$.exitcode>, :type(Int), :package($Exception), :has_accessor));
$Exception.HOW.add_method($Exception, 'message', my method { 'Shell command ⟨' ~ self.^name ~ '⟩ finished with non-zero exitcode of ' ~ self.exitcode ~ '.' } );

$Exception.HOW.compose($Exception);

We use the MOP to create a type at runtime and fill it with attributes and the message method that all Exceptions need. To be able to use this new type in the rest of the program, we need to have that runtime to happen before the rest of the program is compiled. To do so we can use a BEGIN phaser. We can then stuff the new type into an appropriate package.

BEGIN {
    package X::Shell {}
    X::Shell::<ls> := $Exception;
}

As we have access to OUR at BEGIN-time we can also add subs to the script’s scope that can be used in the rest of the program or add our shell commands to a dedicated package to avoid conflicts.

Shell::«$cmd-name» := sub (|c) {
    my $proc = run $cmd-name, |c, :out, :err;
    my $e = X::Shell::«$cmd-name»;
    $e.new(:exitcode($proc.exitcode), :stderr($proc.err.slurp)).throw if $proc.exitcode != 0;

    $proc.out.slurp;
}

Since we put exceptions into X::Shell we can handle exceptions for all shell commands or just a specific one.

Shell::ls('/home/not there', '-l');

CATCH {
    when X::Shell::ls { warn .stderr, .backtrace }
    when X::Shell { warn .^name, ': ', .message; .resume }
}
# OUTPUT: ls: cannot access '/home/not there': No such file or directory
#
#      in block  at exceptional-run.raku line 48

By capturing STDERR and stuffing it into an exception I can choose to expose the error message issued by the shell command to the user of my program and show a stracktrace if I like. The latter can help greatly with debugging. Since I’m really good at writing bugs that will come in handy.

The whole example script can be found here.

Dynamic languages come with a noticeable speed penalty. When we execute them. When we write them we can progress much faster.

Categories: Raku
  1. No comments yet.
  1. June 22, 2020 at 13:35

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: