对于多次调度要记住的关键是它会发生after子或方法解析已发生。因此,所有多重调度实际上是一个两步过程。这两个步骤也是相互独立的。
当写这样的东西时:
multi sub foo($x) { }
multi sub foo($x, $y) { }
编译器会生成一个:
proto sub foo(|) {*}
也就是说,除非你写了一个proto
自己分。这proto
是实际安装到 lexpad 中的内容; Amulti
sub 永远不会直接安装到 lexpad 中,而是安装到 的候选列表中proto
.
因此,当调用multi
子,流程为:
- 使用词法查找查找要调用的子程序,解析为
proto
- 致电
proto
,选择最好的multi
候选人并称之为
当有multi
嵌套范围内的候选人,proto
来自外部作用域的将被克隆并安装到内部作用域中,并将候选者添加到克隆中。
多种方法都会发生非常相似的过程,除了:
- 多种方法只是存储在待办事项列表中,直到结束
}
类、角色或语法的
- A
proto
可以由角色或类提供,因此用以下方式组合角色multi
候选人也只需将他们添加到待办事项列表中
- 最后,如果有多种方法没有
proto
,但是父类有这样一个proto
,将被克隆;否则为空proto
将被制作
这意味着对多方法的调用是:
- 使用通常的方法分派算法查找方法(仅使用 C3 方法解析顺序搜索类),该算法解析为
proto
- 致电
proto
,选择最好的multi
候选人并称之为
完全相同的排序和选择算法用于多子方法和多方法。就多重分派算法而言,调用者只是第一个参数。此外,Perl 6 多重分派算法并不比后面的参数更重视前面的参数,所以就像:
class A { }
class B is A { }
multi sub f(A, B) { }
multi sub f(B, A) { }
将被视为绑定,如果使用以下命令调用,则会给出不明确的调度错误f(B, B)
,定义也是如此:
class B { ... }
class A {
multi method m(B) { }
}
class B is A {
multi method m(A) { }
}
然后打电话B.m(B)
,因为多重发射器再次只看到类型元组(A, B)
and (B, A)
.
多重调度本身就与狭隘性的概念有关。如果 C1 的至少一个参数的类型比 C2 中相同位置的参数窄,并且所有其他参数都相等(即不更窄,也不更宽),则候选 C1 比 C2 窄。如果反之亦然,则它更宽。否则,就被绑住了。一些例子:
(Int) is narrower than (Any)
(Int) is tied with (Num)
(Int) is tied with (Int)
(Int, Int) is narrower than (Any, Any)
(Any, Int) is narrower than (Any, Any)
(Int, Any) is narrower than (Any, Any)
(Int, Int) is narrower than (Int, Any)
(Int, Int) is narrower than (Any, Int)
(Int, Any) is tied with (Any, Int)
(Int, Int) is tied with (Int, Int)
多重匹配器构建候选者的有向图,其中只要 C1 比 C2 窄,就存在从 C1 到 C2 的边。然后,它找到所有没有传入边缘的候选者,并将其删除。这是第一组候选人。删除将产生一组没有传入边缘的新候选集,然后将其删除并成为第二组候选集。这种情况一直持续到从图中取出所有候选者为止,或者到达无法从图中取出任何内容的状态(这是一种非常罕见的情况,但这将作为循环性报告给程序员)。这个过程发生once,而不是每次调度,它会产生一组候选人。 (是的,这只是一种拓扑排序,但分组细节对于接下来的内容很重要。)
当呼叫发生时,系统会按顺序搜索组以查找匹配的候选者。如果同一组的两名候选人比赛,并且没有决胜局(命名参数,where
条款或暗示where
条款来自subset
类型、解包或is default
) 那么将会报告一个不明确的调度。如果搜索完所有组都没有找到结果,则调度失败。
还有一些关于数量的狭隘性考虑(必需参数击败可选参数或slurpy)和is rw
(它比其他方面平等的候选人要窄,但没有is rw
).
一旦发现一组中的一名或多名候选人匹配,就会考虑决胜局。其中包括命名参数的存在,where
条款、解包,并在首场比赛获胜的基础上工作。
multi f($i where $i < 3) { } # C1
multi f($i where $i > 1) { } # C2
f(2) # C1 and C2 tied; C1 wins by textual ordering due to where
请注意,此文本顺序仅适用于平局决胜;就类型而言,源代码中候选者的顺序并不重要。 (命名参数也仅充当平局决胜者,有时会令人惊讶。)
最后,我要指出的是,虽然多重分派的结果始终与我描述的两步过程相匹配,但实际上会发生大量的运行时优化。虽然所有查找最初都完全按照描述进行解析,但结果会被放入调度缓存中,这比搜索拓扑排序提供的组提供的查找速度要快得多。它的安装方式可以完全绕过原型的调用,从而节省调用帧。您可以看到此行为的伪影,如果您--profile
;自动生成的proto
对于任何基于类型的调度(没有决胜局),与多个候选人相比,将收到少量的呼叫。如果您在中编写自定义逻辑,则这不适用proto
, 当然。
除此之外,如果您在 MoarVM 上运行,动态优化器可以走得更远。它可以使用收集的和推断的类型信息来解析方法/子调度and多调度,将 2 步流程变成 0 步流程。小候选人也可以内联到调用者中(同样,探查器可以告诉您内联已经发生),这可以说将多分派变成了 -1 步骤过程。 :-)