包裹异常

楽土

如前所述,我希望减少 Shell::Piping 中简单的错误处理。下面的内容太长了,不适合放在终端的单行中。

CATCH {
    when X::Shell::CommandNotFound {
        when .cmd ~~ ‚commonmarker‘ {
            put ‚Please install commonmarker with `gem install commonmarker`.‘;
            exit 2;
        }
        default {
            .rethrow;
        }
    }
}

我们在第3行有一个条件,第4行有一个 print 语句。剩下的就是样板。我想出的最短的语法是这样的。

X::Shell::CommandNotFound.refine({.cmd eq ‚commonmarker‘}, {„Please install ⟨{.cmd}⟩ in ⟨{.path}⟩.“});
X::Shell::CommandNotFound.refine({.cmd eq ‚raku‘}, {„Please run `apt install rakudo`.“});

我想修改 CommandNotFound 后面的类型对象,因为模块会创建它们的实例,并在我拦截它们之前抛出它们。当然我可以抓住它们。但整个练习是为了摆脱 catch 块。在模块内部为任何异常添加一个方法,从而在编译时和运行时添加一个方法会很好。我们可以通过一个 trait 来实现。

multi sub trait_mod:<is>(Exception:U $ex, :$refineable) {
    $ex.HOW.add_method($ex, ‚refine‘, my method (&cond, &message) {
        $ex.WHO::<@refinements>.append: &cond, &message;
        state $wrapped;
        $ex.HOW.find_method($ex, ‚message‘).wrap(my method {
            my @refinements := self.WHO::<@refinements>;
            for @refinements -> &cond, &message {
                if cond(self) {
                    return message(self);
                }
            }
            nextsame
        }) unless $wrapped;
        $wrapped = True;
    });
}

特质是一个花哨的子例程,它可以接受类型对象或其他静态对象(Raku 是一种动态语言,没有静态的东西)并对其进行操作。这可以是包装-一个变相的 MOP 操作-或者实际的 MOP 调用。这个重新定义的特质通过在类型的 package 部分的 autovification 增加了一个名为 @refinements 的类属性。这被隐藏在伪方法 .WHO.WHO 后面。然后,它增加了一个方法,它接受一对 Callable,并将它们存储在 @refinements 中。类型对象原来的 .message 方法被封装了,除非已经这样做了。这是由一个状态变量控制的。

封装后的 .message 将调用第一个可调用的方法,并期望返回一个 Bool。如果那个 BoolTrue,则会调用第二个可调用的方法。两者都会被调用一个异常实例。

该 trait 不会 .^compose 它被调用的类型对象。这是(目前)需要的,因为下面的情况。

class X::Shell::CommandNotFound is Exception is refineable { ... }

这个特性实际上是在 Exception 上调用的。这样做的原因是,新增的方法 .refine 没有任何阴影。因此,我们可以在编译后使用这个方法等待它被调用来完成我们需要做的事情。如果没有条件匹配,包装器通过 nextsame 有一个落入。

我想我还没有把所有的角落情况都覆盖到。这需要进一步测试。面向用户的语法可以保持原样,因为 traits 是解样板化。特质本身可能会变得更复杂。但这也没什么。负担应该是在实现者这边。

comments powered by Disqus