Wait the light to fall

假定的可预测性

焉知非鱼

assumed predictability

Vadim 不同意我的观点。他也不应该同意。我几乎从不同意自己的观点。另外,我很高兴他的不同意,因为这让我可以写一个我已经排了很久的题目。

基本的说法是,强制类型允许在编译时,甚至可能更早 - 在大脑时 - 对接口进行推理。在静态类型的语言中,这是一件合理的事情。当涉及到对象时,Raku 会在运行时进行大量的思考。让我们来看看两个例子。

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'

我们要求编译器对 Foo 进行类型检查,使 Bar 满足。然后,我们继续对一个只由 Foo 提供的 Bar 实例调用一个方法。编译器试图帮助我们,但不能。在这个例子中,我们的接口不是 Foo 及其方法,而只是 Foo - 不包括它的子类 - 及其方法。这是一个微妙的差异,会在运行时伤害我们。通过另一个例子,我可以很清楚地说明原因。

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

你能说出 sub foo 使用的接口的名称吗?这当然是个刻薄的问题,因为提供接口的类没有名字。类型约束提供了 $.the-object 和任何从 Any 继承的访问器方法的接口。后者可能会随着新的语言版本而改变。因此,真正的接口更像是 *.any-name(),类型约束是 Any。这两个是简单的例子。你使用的任何模块都可能 use MONKEY-TYPING 或者摆弄 MOP。运行时接口在 Raku 中是完全不可预测的,我们都最好 use Test

也就是说,Vadim 坚持最小惊喜的原则是对的。我们用大写字母开始角色的名字,以表示它是一个类型对象,从而希望尊重某种接口。我很乐意为"浆糊"强转的问题提供一个更通用的解决方案。从技术上讲,Raku 已经得到了它的语法。

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!";
}

这种形式基本上会说:“我想让一个叫 Filish 的子系统来处理胁迫”。这将允许 Filish 中的代码被重用,从而提供灵活性,而不会给人以承诺接口的印象。至少在这个例子中,强转 sub 的签名包含了自己的文档。甚至可能还有一些编译时检查的空间,只要我们不使用多。参数 $handle 必须满足 Filish 的签名。有一个 sub 将允许模块用户对其进行 .wrap

与自己一般的分歧是很有挑战性的,但确实让我很容易改变自己的想法。由于这很可能是我今年的最后一篇博文,我祝愿大家在 2021 年有专属的好理由改变自己的想法。