对象、角色和多重分派

Object, roles and multiple dispatch

我正在尝试使用多重分派来重载和使用组合 类 中的方法。这是实现:

role A {
    has $!b;

    submethod BUILD( :$!b ) {}

    multi method bar () {
    return $!b;
    }
}

class B does A {

    submethod BUILD( :$!b ) {}

    multi method bar() {
    return " * " ~ callsame ~ " * ";
    }
}

my $a = A.new( b => 33);
say $a.bar();
my $b = B.new( b => 33 );
say $b.bar();

然而,这失败了:

Calling callsame(Str) will never work with declared signature ()

(我真的不知道为什么callsame使用Str作为签名)。将 method bar 更改为使用 callwith:

multi method bar() {
    return " * " ~ callwith() ~ " * ";
}

根本行不通:

Use of Nil in string context
  in method bar at multi.p6 line 18
 *  *

在 roles/classes 中使用 call* 有什么特别的方法吗?

第一个问题是语法问题。一个 listop 函数调用在它之后解析一个参数列表,从一个术语开始,所以这个:

return " * " ~ callsame ~ " * ";

这样的群组:

return " * " ~ callsame(~ " * ");

因此您在“*”上调用 ~ 前缀运算符,这是它抱怨的 Str 参数的来源。

然而,归根结底,这里的问题是对角色组合语义的误解 and/or 延迟。考虑一个非 multi 的情况:

role R { method m() { say 1; callsame() } }
class B { method m() { say 2; callsame() } }
class C is B does R { method m() { say 3; callsame(); } }
C.m

这输出:

3
2

注意 1 是如何从未达到的。这是因为角色组合是 扁平化 :就好像来自角色的代码被放入了 class。当 class 已经有一个同名的方法时,它会优先于角色中的方法。

如果我们把 multi 放在他们每个人身上:

role R { multi method m() { say 1; callsame() } }
class B { multi method m() { say 2; callsame() } }
class C is B does R { multi method m() { say 3; callsame(); } }
C.m

行为被保留:

3
2

因为角色作曲者占multi method长名——即占签名。由于它们完全相同,因此 class 中的那个获胜。如果它同时保留两者,我们将以初始调用结束,导致不明确的调度错误!

Deferral nextsamecallsamenextwithcallwith 都遍历了我们本可以派遣到的可能对象。

在非multi method的情况下,这是通过走MRO实现的;由于角色中的方法未组合,因此它不会出现在 MRO 中的任何 class 中(只有 classes 不会出现在 MRO 中,因为角色在组合时被展平了) .

multi method 的情况下,我们取而代之的是遍历那些会接受初始调度参数的候选集。同样,由于在 class 中选择了相同的长名称方法以支持组合时的角色,因此角色中的方法根本不首先考虑调度:它不是' t在proto的候选名单中,所以不会推迟到。