Chain calling
When working with IO::Path
we have to take platform dependent directory separator into account. To alleviate the problem .add
was added. Surprisingly there is no candidate that takes a list. So we have to chain method calls as if we would use an inferior language.
'/home/dex'.IO.chain.add('tmp').add('foo.txt').say;
"/home/dex/tmp/foo.txt".IO
Since it is rather unlikely that we ever do calculations with IO::Path
objects we can repurpose infix:</>
. Doing so gives us metaoperators for free.
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("/"))
Having to chain method calls because the implementer was lazynot overly imaginative is a common theme. Since the default parent class is Any
, any method added to that class should show up everywhere.
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
The method chain
actually breaks the chain by returning an instance of a private class. This object knows the original object and will change any method call to a loop over the first positional. Named arguments are forwarded via the implicit argument %_
.
You likely spotted both “should” and .HOW.compose
. This is a long-standing issue. The MOP does keep a list of parent classes but not for children. So neither the compiler nor we can easily walk all type objects to recompose them. It’s a bit of a shame. There is likely much more that could be done in raku.land with a properly working augment
.
-
January 25, 2021 at 19:342021.04 Grant Reporting – Rakudo Weekly News