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 Exception
s 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 Exception
s 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.
-
June 22, 2020 at 13:352020.24 Cloud Approaching – Rakudo Weekly News