Wait the light to fall

关于强转方法的返回值

焉知非鱼

on coercion method return value

Wenzel P.P. Peppmeyer 继续着他关于 Raku 的精彩博文,他涉及了一些非常有趣的主题。他的最后一篇文章是关于在一个模块中实现 DWIM 原则,以使用户减少对模板代码的关心。单单这一点就不会让我写这篇文章,但 Wenzel 提出了一个我以为会引起困惑的问题。而显然我没有看错。

以下是帖子中的一段话。

新的强转协议非常有用,但有一个缺陷:它强迫我返回包含 COERCE-method 的类型。在一个没有太大意义的角色中,它迫使我与 IO::Handle 打交道。

基本上,这个要求归结为下面的片段。

role Foo {
    multi method COERCE(Str:D $s) {
        $s.IO.open: :r
    }
}
sub foo(Foo() $v) {
    say $v.WHICH;
}
foo($?FILE);

试图运行它,会得到。

Impossible coercion from ‘Str’ into ‘Foo’: method COERCE returned an instance of IO::Handle.

我们来看看为什么这里的错误是合法的。

简短的答案是:强转,虽然在某种程度上是放松的,但最上是一种类型约束。

较长的答案是:用户期望 Foo 在 $v 中的 sub foo。我提议做一个小的思想实验,就是在 Foo 这个角色中加入一个方法。例如,我们想在文件中用空格来填充文本。为此,我们在角色 Foo 中实现 method shift-right(Int:D $columns) {...}。然后我们在 sub foo 中使用该方法。

sub foo(Foo() $handle) {
    ...
    $handle.shift-right(4);
    ...
}

$handle 不是 Foo 时,需要我详细说明会发生什么吗?

下面是我要做的角色版本:

subset Pathish of Any:D where Str | IO::Handle;

role Filish[*%mode] is IO::Handle {
    multi method COERCE(IO:D(Pathish) $file) {
        self.new(:path($file)).open: |%mode
    }
}

sub prep-file( Filish[:r, :!bin]() $h, 
               Str:D $pfx ) 
{
    $h.lines.map($pfx.fmt('%-10s: ') ~ *)».say;
}

prep-file($?FILE, "Str");
prep-file($?FILE.IO, "IO");
prep-file($?FILE.IO.open(:a), "IO::Handle");

注意使用强转来实现强制类型转换。这个想法是把任何东西,可以变成一个 IO 实例。

另外,我强行重新打开任何 IO::Handle,因为源头的打开模式可能与我期望的强转结果不同。在我的例子中,我故意将一个以 append 模式打开的句柄传入一个期望读取句柄的 sub 中。

最后我想说的是,在这里我们有一个很好的例子,Raku 允许 DWIM 代码语义而不破坏其可预测性。