多重分派要记住的关键是它发生在子或方法解析发生之后。所以所有的多次调度实际上是一个两步的过程。这两个步骤也是相互独立的。
在写类似的东西时:
multi sub foo($x) { }
multi sub foo($x, $y) { }
编译器将生成:
proto sub foo(|) {*}
也就是说,除非你proto
自己写了一个子。这proto
是实际安装到 lexpad 中的内容;sub 永远不会直接安装到multi
lexpad 中,而是安装到proto
.
所以,调用multi
sub的时候,流程是:
- 使用词法查找找到要调用的子程序,它解析为
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)
,因为 multi-dipsatcher 再次只看到类型元组(A, B)
和(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)
multi-dipsatcher 构建候选者的有向图,其中只要 C1 比 C2 窄,就会有一条从 C1 到 C2 的边。然后它找到所有没有传入边的候选,并将它们删除。这是第一批候选人。删除将产生一组没有传入边的新候选,然后将其删除并成为第二组候选。这种情况一直持续到所有候选者都从图中取出,或者如果我们达到无法从图中取出任何内容的状态(非常罕见的情况,但这将作为循环报告给程序员)。这个过程发生一次,而不是每次调度,它会产生一组候选组。(是的,这只是一种拓扑排序,但分组细节对于接下来的内容很重要。)
发生呼叫时,将搜索组以查找匹配的候选人。如果同一组中的两个候选人匹配,并且没有决胜局(命名参数、子句或来自类型、解包或的where
隐含子句),则将报告模棱两可的调度。如果搜索所有组而没有找到结果,则分派失败。where
subset
is default
关于 arity 也有一些狭隘的考虑(必需的参数比可选参数或 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
请注意,此文本排序仅适用于平局;就类型而言,源代码中候选的顺序并不重要。(命名参数也仅充当决胜局,有时令人惊讶。)
最后,我要指出,虽然多次分派的结果将始终与我所描述的两步过程相匹配,但实际上发生了大量的运行时优化。虽然所有查找最初都完全按照描述进行解析,但结果被放入调度缓存中,它提供的查找速度比搜索拓扑排序提供的组快得多。这是以这样一种方式安装的,即可以完全绕过 proto 的调用,从而节省调用帧。如果您 ; 您可以看到此行为的伪影--profile
;与多个候选者相比,为任何基于类型的调度(没有决胜局)自动生成proto
的调用将收到少量调用。当然,如果您在 中编写自定义逻辑,这将不适用proto
。
除此之外,如果你在 MoarVM 上运行,动态优化器可以走得更远。它可以使用收集和推断的类型信息来解决方法/子分派和多分派,将2步过程变成0步过程。小的候选人也可以内联到调用者中(同样,分析器可以告诉您内联已经发生),这可以说将多调度变成了 -1 步过程。:-)