Perl 6 对象如何找到可能在父类或角色中的多方法? [英] How does a Perl 6 object find a multi method that might be in a parent class or role?

查看:50
本文介绍了Perl 6 对象如何找到可能在父类或角色中的多方法?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

考虑这个例子,其中一个子类有一个没有签名的 multi 方法和一个带有 slurpy 参数的方法:

Consider this example where a subclass has a multi method with no signature and one with a slurpy parameter:

class Foo {
    multi method do-it { put "Default" }
    multi method do-it ( Int $n ) { put "Int method" }
    multi method do-it ( Str $s ) { put "Str method" }
    multi method do-it ( Rat $r ) { put "Rat method" }
    }

class Bar is Foo {
    multi method do-it { put "Bar method" }
    multi method do-it (*@a) { put "Bar slurpy method" }
    }

Foo.new.do-it: 1;
Foo.new.do-it: 'Perl 6';
Foo.new.do-it: <1/137>;
Foo.new.do-it;

put '-' x 10;

Bar.new.do-it: 1;
Bar.new.do-it: 'Perl 6';
Bar.new.do-it: <1/137>;
Bar.new.do-it: 5+3i;
Bar.new.do-it;

方法查找的结构如何?我正在寻找更多的方法来解释它,特别是不要抱怨它.

How is the method lookup structured? I'm looking more for a way to explain it and specifically not complaining about it.

Int method
Str method
Rat method
Default
----------
Int method
Str method
Rat method
Bar slurpy method
Bar method

例如,使用 1 调用 Bardo-it.一些理性的人可能会认为它首先在 Bar 中寻找匹配的签名,而 slurpy 永远不会让任何东西通过它.然而,该调用会在继承链中找到正确的 multi.

There's a call to Bar's do-it with 1 for instance. Some reasonable people might think that it looks for a matching signature in Bar first and that slurpy would never let anything get past it. Yet, the call finds the right multi in the inheritance chain.

Bar 是否已经知道所有的签名?它是搜索还是所有这些东西在组合时都已经解决了?

Does Bar already know all the signatures? Does it search or is all of that stuff already resolved when it is composed?

还有,有没有办法在运行时找出哪个类提供了该方法?也许有一些呼唤如何?当我有一个错误指定的 multi 并且正在其他地方处理时,这将是一个方便的调试工具.

And, is there a way to find out at run time which class provided the method? Maybe with some call into HOW? This would be a handy debugging tool when I have a multi I've incorrectly specified and is being handled elsewhere.

推荐答案

要记住多重分派的关键是它发生在 子或方法解析发生之后.所以所有的多重分派实际上是一个两步过程.这两个步骤也是相互独立的.

The key thing to keep in mind with multiple dispatch is that it happens after sub or method resolution has taken place. So all multiple dispatch is actually a two step process. The two steps are also independent of each other.

写一些类似的东西时:

multi sub foo($x) { }
multi sub foo($x, $y) { }

编译器会生成一个:

proto sub foo(|) {*}

也就是说,除非你自己写了一个 proto 子.proto 是实际安装到 lexpad 中的内容;multi sub 永远不会直接安装到 lexpad 中,而是安装到 proto 的候选列表中.

That is, unless you wrote a proto sub by yourself. The proto is what actually gets installed into the lexpad; a multi sub is never installed directly into the lexpad, but instead installed into the candidates list of the proto.

所以,当调用一个multi子时,过程是:

So, when calling a multi sub, the process is:

  1. 使用词法查找查找要调用的 sub,该查找解析为 proto
  2. 调用 proto,它选择最佳的 multi 候选并调用它
  1. Find the sub to call using a lexical lookup, which resolves to the proto
  2. Call the proto, which picks the best multi candidate and calls it

当嵌套作用域中有 multi 候选时,来自外部作用域的 proto 将被克隆并安装到内部作用域中,并将候选添加到克隆中.

When there are multi candidates in nested scopes, the proto from an outer scope will be cloned and installed into the inner scope, and the candidate added to the clone.

一个非常相似的过程发生在多个方法中,除了:

A very similar process happens with multi methods, except:

  • 多个方法只是存储在一个待办事项列表中,直到类、角色或语法的结束}
  • 一个 proto 可以由一个角色或一个类提供,所以用 multi 候选组合一个角色只需将它们添加到待办事项列表中
  • 最后,如果有多个没有proto的方法,但是父类有这样的proto,就会被克隆;否则将生成一个空的 proto
  • Multi methods are just stored up in a todo list until the closing } of the class, role, or grammar
  • A proto may be provided by a role or a class, so composing a role with multi candidates just adds them to the todo list also
  • Finally, if there is multi methods with no proto, but a parent class has such a proto, that will be cloned; otherwise an empty proto will be made

意味着对多方法的调用是:

Meaning that a call to a multi-method is:

  1. 使用通常的方法调度算法(仅使用 C3 方法解析顺序搜索类)查找方法,该算法解析为 proto
  2. 调用 proto,它选择最佳的 multi 候选并调用它
  1. Find the method using the usual method dispatch algorithm (which just searches classes using the C3 Method Resolution Order), which resolves to the proto
  2. Call the proto, which picks the best multi candidate and calls it

多子和多方法使用完全相同的排序和选择算法.就多重调度算法而言,调用者只是第一个参数.此外,Perl 6 多分派算法不会比后面的参数更重地加权较早的参数,所以就像:

The exact same sorting and selection algorithm are used for both multi subs and multi methods. The invocant, so far as the multiple dispatch algorithm cares, is just the first parameter. Furthermore, the Perl 6 multiple dispatch algorithm does not weight earlier arguments more heavily than later ones, so just as:

class A { }
class B is A { }
multi sub f(A, B) { }
multi sub f(B, A) { }

如果使用 f(B, B) 调用,会被认为是绑定的,并给出不明确的调度错误,定义也是如此:

Would be considered tied, and give an ambiguous dispatch error if called with f(B, B), so would defining:

class B { ... }
class A {
    multi method m(B) { }
}
class B is A {
    multi method m(A) { }
}

然后调用 Bm(B),因为再次多分配器只看到类型元组 (A, B)(B, A).

And then calling B.m(B), since again the multi-dipsatcher just sees the type tuples (A, B) and (B, A).

Multiple dispatch 本身就涉及到窄的概念.如果 C1 的至少一个参数是比 C2 中相同位置的参数更窄的类型,并且所有其他参数是绑定的(即,不是更窄,不是更宽),则候选 C1 比 C2 更窄.如果逆为真,则它更宽.否则,它是绑定的.一些例子:

Multiple dispatch itself is concerned with the concept of narrowness. A candidate C1 is narrower than C2 if at least one argument of C1 is a narrower type than the argument in the same position in C2, and all other arguments are tied (that is, not narrower, not wider). If the inverse is true then it is wider. Otherwise, it is tied. Some examples:

(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 的边.然后它找到所有没有传入边的候选者,并删除它们.这是第一批候选人.移除将产生一组没有传入边的新候选者,然后将其移除并成为第二组候选者.这一直持续到从图中取出所有候选,或者如果我们达到无法从图中取出任何东西的状态(一种非常罕见的情况,但这将作为循环报告给程序员).这个过程发生一次,而不是每次分派,它会产生一组候选人.(是的,这只是一种拓扑排序,但分组细节对于接下来的内容很重要.)

The multi-dipsatcher builds a directed graph of the candidates, where there is an edge from C1 to C2 whenever C1 is narrower than C2. It then finds all of the candidates with no incoming edges, and removes them. These are the first group of candidates. The removal will produce a new set of candidates with no incoming edges, which are then removed and become the second group of candidates. This continues until all candidates are taken from the graph, or if we reach a state where we can take nothing from the graph (a very rare situation, but this will be reported to the programmer as a circularity). This process happens once, not per dispatch, and it produces a set of groups of candidates. (Yes, it is just a topological sort, but the grouping detail is significant for what comes next.)

发生呼叫时,将搜索组以查找匹配的候选人.如果同一组中的两个候选匹配,并且没有决胜局(命名参数、where 子句或来自 subset 类型的隐含 where 子句, unpacks, or is default) 然后会报告一个模棱两可的调度.如果搜索了所有组都没有找到结果,则调度失败.

When a call happens, the groups are searched in order for a matching candidate. If two candidates in the same group match, and there are no tie-breakers (named parameters, where clauses or implied where clauses from subset types, unpacks, or is default) then an ambiguous dispatch will be reported. If all the groups are searched without a result being found, then the dispatch fails.

关于arity(必需参数比可选参数或slurpy)和is rw(它比没有is rw).

There are also some narrowness considerations with regard to arity (required parameter beats optional parameter or slurpy) and is rw (it's narrower than an otherwise equal candidate without is rw).

一旦发现一组中的一个或多个候选人匹配,就会考虑决胜局.这些包括命名参数、where 子句和解包的存在,并在第一场比赛获胜的基础上工作.

Once one or more candidates in a group have been found to match, then tie-breakers are considered. These include the presence of named parameters, where clauses, and unpacks, and work on a first-match-wins basis.

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

请注意,此文本排序仅适用于决胜局;就类型而言,源代码中候选的顺序并不重要.(命名参数也只是作为决胜局,有时令人惊讶.)

Note that this textual ordering is only applicable to the tie-breaking; so far as types go, the order of candidates in the source code is not important. (That named parameters also act only as tie-breakers is sometimes a source of surprise.)

最后,我会注意到,虽然多次分派的结果将始终与我描述的两步过程相匹配,但实际上会发生大量的运行时优化.虽然所有查找最初都完全按照描述进行解析,但结果被放入调度缓存中,这提供了比搜索由拓扑排序交付的组更快的查找.这种安装方式可以完全绕过 proto 的调用,从而节省调用帧.如果你 --profile,你可以看到这种行为的工件;与多候选者相比,任何基于类型的调度(没有 tie-breakers)自动生成的 proto 将收到少量调用.当然,如果您在 proto 中编写自定义逻辑,则这不适用.

Finally, I'll note that while the results of a multiple dispatch will always match the 2-step process I've described, in reality a good amount of runtime optimization takes place. While all lookups are initially resolved exactly as described, the outcome is placed into a dispatch cache, which provides much faster lookups than searching the groups delivered by the topological sort could. This is installed in such a way that the call of the proto can be entirely bypassed, saving a callframe. You can see artifacts of this behavior if you --profile; the auto-generated proto for any type-based dispatch (without tie-breakers) will receive a tiny number of calls compared to the multi candidates. This doesn't apply if you write custom logic in your proto, of course.

除此之外,如果您在 MoarVM 上运行,动态优化器可以走得更远.它可以使用收集和推断的类型信息来解析方法/子调度多调度,将两步过程变成 0 步过程.小的候选也可以被内联到调用者中(同样,分析器可以告诉你内联已经发生),这可以说是将多分派变成了 -1 步过程.:-)

Beyond that, if you're running on MoarVM, the dynamic optimizer can go a bunch further. It can use collected and inferred type information both to resolve the method/sub dispatch and the multi dispatch, turning a 2-step process into a 0-step process. Small candidates can be inlined into the caller also (again, the profiler can tell you that the inlining has happened), which arguably turns a multi-dispatch into a -1 step process. :-)

这篇关于Perl 6 对象如何找到可能在父类或角色中的多方法?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆