在拉里看来,懒惰是一种美德。让我们看看 Rakudo 是否同意。
sub virtue { }; lazy virtue;
# OUTPUT: Nil
几篇文章之前,我曾对 run
、shell
和 qx{}
缺乏 Exceptions 感到惋惜。后来我改变了主意。这两个构造都不知道它们在什么操作系统上运行,也不知道 shell 命令是什么。程序员却知道(傲慢)。
这让我很不耐烦。当 Proc.exitcode
为非零时,编译器拒绝为我创建一个异常。在 Raku 中,这是一个可以解决的问题。
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);
我们使用 MOP 在运行时创建一个类型,并在其中填充所有 Exceptions 所需要的属性和消息方法。为了能够在程序的其余部分使用这个新类型,我们需要在程序的其余部分被编译之前让这个运行时发生。要做到这一点,我们可以使用一个 BEGIN
phaser。然后我们可以将新类型塞进一个合适的包中。
BEGIN {
package X::Shell {}
X::Shell::<ls> := $Exception;
}
由于我们在 BEGIN
时可以访问 OUR
,所以我们也可以在脚本的作用域中添加子程序,可以在程序的其他部分使用,或者将我们的shell命令添加到一个专门的包中,以避免冲突。
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;
}
因为我们把异常放到了 X::Shell
中,所以我们可以处理所有shell命令的异常,也可以只处理某个特定的异常。
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
通过捕获 STDERR 并将其塞入异常中,我可以选择将shell命令发出的错误信息暴露给我的程序用户,如果我愿意的话,还可以显示栈跟踪。后者对调试有很大的帮助。由于我非常擅长写 bug,这将会很方便。
整个示例脚本可以在这里找到。
动态语言会有一个明显的速度惩罚。当我们执行它们的时候。当我们写它们的时候,我们可以进步得更快。
by gfldex