【问题标题】:How does Perl 6's multi dispatch decide which routine to use?Perl 6 的多分派如何决定使用哪个例程?
【发布时间】:2017-06-19 02:24:40
【问题描述】:

考虑这个程序,我在参数列表中构造了一个数组。虽然有一个接受 Array 的签名,但这会调用接受 List 的签名:

foo( [ 1, 2, 3 ] );

multi foo ( Array @array ) { put "Called Array @ version" }
multi foo ( Array $array ) { put "Called Array \$ version" }
multi foo ( List $list )   { put "Called List version" }
multi foo ( Range $range ) { put "Called Range version" }

我从一个意外的例程中得到输出:

Called Array $ version

如果我取消注释另一个签名,则调用该签名:

Called List version

为什么不调用( Array @array ) 版本?调度员如何做出决定(以及记录在哪里)?

【问题讨论】:

  • cf say (-> Array @ {}).signature.params[0].type

标签: signature raku multidispatch


【解决方案1】:

为什么不调用 (Array @array) 版本?

您的测试 foo 调用只有一个数组 ([1,2,3]) 作为其参数,而不是 of Arrays 的数组(例如 [[1,2,3],[4,5,6]])。

(@array 中的 @ 表示 does Positional 的值,例如数组或列表。Array @array 表示相同的东西,但有一个额外的约束,即数组、列表或其他任何元素的每个元素是一个Array。)

调度员如何做出决定?

简化,它选择最窄的匹配类型:

multi foo ( Array       )              {} # Narrowest
multi foo ( List        )              {} # Broader
multi foo ( Positional  )              {} # Broader still
multi foo ( @array      )              {} # Same as `Positional`

(Diagram of subtype relationships of Array, List and Positional.)

更多详情请见jnthn's authoritative answer to a related SO question

(它记录在哪里)?

我不确定文档。 Multi-dispatch 看起来很小。

【讨论】:

    【解决方案2】:

    我犯了一个非常愚蠢的错误,这就是为什么我没有看到我所期望的。您不能约束以@ 开头的变量。任何约束都适用于其元素。 Array @array 表示我有一种位置类型的东西,其中每个元素都是Array。这是the same thing that raiph said。奇怪的是,语法看起来一样,但做的事情却不同。这是我以前绊倒过的东西。

    由于它在做不同的事情,即使数据结构匹配也不会成功:

    foo( [ [1], [2], [3] ] );
    foo( [ 1, 2, 3 ] );
    
    multi foo ( Array @array ) { put "Called Array @ version" }
    multi foo ( Array $array ) { put "Called Array \$ version" }
    multi foo ( List $list )   { put "Called List version" }
    multi foo ( Range $range ) { put "Called Range version" }
    

    根据约束和数据结构,我仍然得到了我不期望的版本:

    Called Array $ version
    Called Array $ version
    

    我认为这只是普通用户必须学习的 Perl 6 的缺点之一。

    【讨论】:

    • Nit:“你不能约束以@开头的变量”。可以,但它必须是使用 where 子句或 subset 类型的动态约束。例如,multi foo ( @ where *[0] > 2 ) {} 将匹配 foo [4,3,2] 调用,但不匹配 foo [2,3,4]
    • “奇怪的是语法看起来一样,但做的事情却不同。”。 Perls 的基础是它们的语法考虑了grammatical number$ 表示单一事物。 $var 容器声明左侧的类型表示该容器中包含的单个事物的类型。 @ 表示复数。 @var 容器声明左侧的类型表示该复数容器中保存的东西的类型。
    • 我写道:“Nit:“你不能约束一个以@开头的变量”。你可以......”你也可以将一个变量绑定到不同于通常默认值的容器类型。例如,my %foo is Bag%foo 绑定到Bag 而不是Hash。这(还没有?)对常规参数(例如sub foo ( @foo is List ) {})起作用,但我决定我不妨彻底地进行挑剔。 :)
    【解决方案3】:

    似乎在设计文档(更完整但更过时)和文档(已知不完整,正如 docs.perl6.org 承认的那样,但希望是最新的)之间进行权衡。前者在Synopsis 12 中解释了多子分辨率。摘录:

    当您调用具有特定短名称的例程时,如果有多个可见的长名称,则它们都被视为候选。根据参数的运行时类型与每个候选参数的声明类型的匹配程度,它们按顺序排序。最好的候选人被调用,除非出现平局,在这种情况下,平局的候选人将使用任何额外的决胜局策略重新分配(见下文)。 [...]

    共有三种决胜模式,按绝望的升序排列:

    A) 内部或派生范围

    B) 运行时约束处理

    C) 使用标有“默认”的候选者

    Tiebreaker A 只是更喜欢内部或更多派生范围中的候选者,而不是外部或更少派生范围中的候选者。对于同一范围内的候选人,我们继续进行决胜局 B。

    在没有任何限制的情况下,决胜局 A 中的平局立即故障转移到决胜局 C;如果没有被 C 解决,它们会在编译时警告不明确的分派。 [...]

    我对 Perl 6 的了解不足以证明它的准确性,但它似乎与 raith’s answer 一致,并且还涵盖了其他方面。

    【讨论】:

    • @briandfoy Imo 第一部分确实涵盖了您展示的行为:“当您调用具有特定短名称的例程时,如果有多个可见的长名称,它们都被视为候选者。它们已排序根据参数的运行时类型与每个候选者的参数声明类型的匹配程度,按顺序排列。最好的候选者被调用,除非有平局......“没有平局,所以候选者与最接近的匹配类型获胜。
    猜你喜欢
    • 1970-01-01
    • 2022-10-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多