在Julia中,如何在调用者提供的(超)类型的参数上正确调度方法?

use*_*730 6 julia

我想定义一个函数f(x, t::Type),根据是否执行不同的行为isa(x, t).让我们说,b1(x)如果是,我想打电话,b2(x)否则.

我知道我可以在运行时进行动态检查,如下所示:

function f(x, t::Type)
  if isa(x, t)
    b1(x)
  else
    b2(x)
  end
end
Run Code Online (Sandbox Code Playgroud)

但是,有没有办法纯粹使用参数化方法调度?例如,如果我定义

f{T}(x::T, t::Type{T}) = b1(x)
f(x, t::Type) = b2(x)
Run Code Online (Sandbox Code Playgroud)

for f(1, Int)f(1.0, Int)调用正确的行为.但我希望这也适用于以下所有子类型t:

f(1, Number)
Run Code Online (Sandbox Code Playgroud)

这实际上是b2因为第一个签名f不匹配.有趣的是,f(x::Number, t::Type{Number}) = b1(x)在这种情况下会匹配.

我错过了一些明显的东西吗?

这显然是一个错误,并在0.4中修复.


问题:

  1. 为什么不f{T}(x::T, t::Type{T})匹配f(1, Number),即使T(Number)的类型替换匹配?

  2. 使用f{T2, T1 <: T2}(x::T1, t::Type{T2})或类似的东西不起作用,因为只有在关闭完整的静态参数列表后,所有静态参数才会出现在范围内.为什么?

  3. 使用动态方法是否会有任何性能损失?

  4. 如何将方法定义为内部函数,所以我可以绑定t到局部变量,如下所示: function f(x, t::Type); g(x::t) = b1(x); g(x) = b2(x); g(x) end

    这有效,但性能成本是多少?

  5. 什么是解决这个问题的惯用/首选方法?

(我曾在0.3.2上试过这个.)

sim*_*ter 5

回答你的问题:

  1. 它应该是AFAICT.正如用户3580870评论的那样,这似乎适用于Julia 0.4.
  2. 这是"三角调度",尚未实施.见#3766.
  3. 要看.编译器可以isa(x, t)在编译时进行评估并消除分支.但是,仍然有一些可能的使用方式isa可能不是最理想的:
    • 如果两个分支返回不同的类型,则类型推断将无法正确推断返回类型,因为它在发生之前发生isa(x, t)为常量.(这可能代价高昂;下面其他可能的去优化可能不是什么大问题.)
    • 如果一个分支执行堆分配但另一个分支不执行,则可能会发出不必要的GC帧.
    • 该函数可能未内联.
  4. 内部函数将具有与使用isa和更多相同的性能问题.从Julia 0.5开始,在这种情况下,内部函数应该与顶级函数一样有效.
  5. 提交错误;-).我认为这应该有效.有可能无论如何固定在0.4中都可以向后移植到0.3.但如果不这样做,isa并不是一个糟糕的方法,只要它不会导致类型推断问题.