关于强转方法的返回值
— 焉知非鱼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 代码语义而不破坏其可预测性。