As Wenzel P.P. Peppmeyer continues with his great blog posts about Raku, he touches some very interesting subjects. His last post is about implementing DWIM principle in a module to allow a user to care less about boilerplate code. This alone wouldn’t make me writing this post but Wenzel is raising a matter which I expected to be a source of confusion. And apparently I wasn’t mistaken about it.
Here is the quote from the post:
The new coercion protocol is very useful but got one flaw. It forces me to return the type that contains the COERCE-method. In a role that doesn’t make much sense and it forces me to juggle with IO::Handle.
Basically, the claim winds down to the following snippet:
role Foo {
multi method COERCE(Str:D $s) {
$s.IO.open: :r
}
}
sub foo(Foo() $v) {
say $v.WHICH;
}
foo($?FILE);
Trying to run it one will get:
Impossible coercion from 'Str' into 'Foo': method COERCE returned an instance of IO::Handle
Let’s see why the error is legitimate here.
The short answer would be: coercion, though relaxed in a way, uppermost is a type constraint.
The longer answer is: the user expects Foo
in $v
in sub foo
. I propose to
do a little thought experiment which involves adding a method to the role Foo
.
For example, we want to pad text in the file with spaces. For this we implement
method shift-right(Int:D $columns) {...}
in role Foo
. Then we use the method
in our sub foo
:
sub foo(Foo() $handle) {
...
$handle.shift-right(4);
...
}
Do I need to elaborate on what’s gonna happen when $handle
is not Foo
?
Here is the version of the role as I would do it:
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");
Note the use of coercion to implement coercion. The idea is to take anything,
what could be turned into an IO
instance.
Also, I forcibly re-open any IO::Handle
because the source could have
different open modes from what I expect to be the result of the coercion. In my
example I’m intentionally passing a handle opened in append mode into a sub
expecting a read handle.
I’d like to finish with a note that here we have a good example of Raku allowing DWIM code semantics without breaking its predictability.
Comments