链式调用
当使用 IO::Path 时,我们必须考虑到依赖于平台的目录分隔符。为了缓解这个问题,我们添加了 .add。令人惊讶的是,没有一个候选的方法可以接受一个列表。所以我们必须像使用劣质语言一样链式调用方法。
'/home/dex'.IO.chain.add('tmp').add('foo.txt').say;
"/home/dex/tmp/foo.txt".IO
由于我们不太可能使用 IO::Path 对象进行计算,因此我们可以重新使用 infix:</>
。这样做可以让我们免费获得元操作符。
multi sub infix:</>(IO::Path:D \p is raw, Str:D \s) {
p.add(s).resolve()
}
multi sub infix:</>(IO::Path:D \p is copy, List:D \l) {
for l {
p = p.add: .Str
}
p
}
my $p = '/home/dex/'.IO;
$p /= 'bar';
dd $p;
# OUTPUT: Path $p = IO::Path.new("/home/dex/bar", :SPEC(IO::Spec::Unix), :CWD("/"))
因为实现者太懒惰而不得不连锁调用方法,这是一个常见的主题。由于默认的父类是 Any,所以添加到该类中的任何方法都应该到处显示。
use MONKEY-TYPING;
augment class Any {
method chain {
class Chainer {
has $.the-object;
method FALLBACK($name, *@a) {
my $o = $.the-object;
for @a -> $e {
$o = $o."$name"($e, |%_);
}
$o
}
}
Chainer.new(the-object => self)
}
}
IO::Path.HOW.compose(IO::Path);
'/home/dex'.IO.chain.add(<tmp foo.txt>).say;
# OUTPUT: "/home/dex/tmp/foo.txt".IO
方法链实际上是通过返回一个私有类的实例来打破链条。这个对象知道原来的对象,并会将任何方法调用改为在第一个位置参数上循环。命名参数通过隐式参数 %_
转发。
你很可能发现了 “should” 和 .how.compose
。这是一个长期存在的问题。MOP 确实保存了父类的列表,但没有为子类保存列表。因此,无论是编译器还是我们都不能轻易地遍历所有类型对象来重组它们。这是一个有点遗憾的问题。在 raku.land 中,有可能有更多的事情可以通过适当的增强来完成。