链式调用

链式调用

当使用 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 中,有可能有更多的事情可以通过适当的增强来完成。

comments powered by Disqus