Home > Raku > Assumed predictability

Assumed predictability

Vadim does not agree with me. Nor should he. I hardly ever agree with myself. Also, I’m happy for his disagreement because it allows me to write about a topic that I got in the queue for quite some time.

The basic statement is that enforcing types allows reasoning about interfaces at compile time and maybe even earlier — at brain time. A reasonable thing to do in a statically typed language. When objects are involved, Raku does plenty of its thinking at runtime. Let’s have a look at two examples.

class Foo {
    submethod shift-right($i) { }
}

sub foo(Foo() $handle) {
    $handle.shift-right(4);
}

foo(Foo.new);

class Bar is Foo {
}

foo(Bar.new);

# OUTPUT: No such method 'shift-right' for invocant of type 'Bar'

We ask the compiler for a type check against Foo that Bar satisfied. Then we proceed to call a method on an instance of Bar that is only supplied by Foo. The compiler tried to help us but couldn’t. In this example our interface is not Foo and its methods but only Foo — excluding its child classes — and its methods. This is a subtle difference that will hurt us at runtime. With another example I can illustrate quite clearly why.

class Catchall {
    has $.the-object;
    method FALLBACK($name, |c) {
        self.the-object."$name"(|c)
    }
}

sub foo(Catchall $c) {
    $c.any-name(42);
}

say foo( Catchall.new: the-object =>
    class :: {
        method any-name($i) { 42 ~~ $i ?? 'Universe' !! 'wut‽'  }
    }
);
# OUTPUT: Universe

Can you name the interface that is being used by sub foo? That’s a mean question of course, as the class that supplies the interface got no name. The type constraint provides the interface of an accessor method to $.the-object and anything inherited from Any. The latter might change with new language versions btw. Consequently, the real interface is more like *.any-name() with a type constraint of Any. Those two are simple examples. Any module you use might use MONKEY-TYPING or fiddle with the MOP. Runtime interfaces are utterly unpredictable in Raku and we all do well to use Test.

That being said, Vadim is right to uphold the principle of least surprise. We start the names of roles with a capital to indicate it to be a type object and thus the wish to honour some sort of interface. I would be happy for a more general solution to the problem of “slurpy” coercion. Technically, Raku got the syntax for it already.

sub Filish(Any:D $handle where * ~~ Str|IO::Handle|IO::Path --> IO::Handle) {
    # ... coerce away here
}

sub foo(&Filish() $handle) {
    $handle.put: "This would make me happy!";
}

This form would basically say: “I want the coercion handled by a sub called Filish“. It would allow the code in Filish to be reused and as such provide flexibility, without giving the impression to promise interface. At least in this example the signature of the coercion sub contains its own documentation. There may even be room for some compile time checks, so long as we don’t use a multi. The parameter $handle must satisfy the signature of Filish. Having a sub would allow a module user to .wrap it.

Being in general disagreement with myself can be challenging but does allow me to change my mind easily. Since this is likely my last blog post this year I wish you all exclusively nice reasons to change your minds in 2021.

Categories: Raku
  1. December 28, 2020 at 01:37
  1. December 28, 2020 at 19:51

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: