朱莉娅无法进行多方法分发。

3

v06 我想编写一个需要2到3个参数的签名。第一个参数可以是整数或整数向量,第二个参数可以是整数向量或整数矩阵。第三个参数可以是整数向量或未指定。

我首先尝试了以下方式:

function foo(
a::Union{Integer, Vector{Integer}},
b::Union{Vector{Integer}, Matrix{Integer}},
c::Union{Void, Vector{Integer}} = nothing)

当我像这样调用 foo(3, [0o7, 0o5]) 时,我会收到一个错误提示,告诉我无法匹配。
ERROR: LoadError: MethodError: no method matching foo(::Int64, ::Array{UInt8,1})
Closest candidates are:
  foo(::Union{Array{Integer,1}, Integer}, !Matched::Union{Array{Integer,1}, Array{Integer,2}}) at ...
  foo(::Union{Array{Integer,1}, Integer}, !Matched::Union{Array{Integer,1}, Array{Integer,2}}, !Matched::Union{Array{Integer,1}, Void}) at ...

现在我明白为什么 julia 无法匹配这个 Array{UInt8} <: Array{Integer} == false,但这似乎并不太聪明。
然后我尝试了这个:
foo(a::Union{Z1, Vector{Z1}},
    b::Union{Vector{Z2}, Matrix{Z2}},
    c::Union{Void, Vector{Z3}} = nothing
    ) where {Z1 <: Integer, Z2 <: Integer, Z3 <: Integer}

现在julia甚至不告诉我哪里不匹配了!
ERROR: LoadError: MethodError: no method matching foo(::Int64, ::Array{UInt8,1}, ::Void)
Closest candidates are:
  foo(::Union{Array{Z1<:Integer,1}, Z1<:Integer}, ::Union{Array{Z2<:Integer,1}, Array{Z2<:Integer,2}}, ::Union{Array{Z3<:Integer,1}, Void}) where {Z1<:Integer, Z2<:Integer, Z3<:Integer} at ...
  foo(::Union{Array{Z1<:Integer,1}, Z1<:Integer}, ::Union{Array{Z2<:Integer,1}, Array{Z2<:Integer,2}}) where {Z1<:Integer, Z2<:Integer} at ...

当你有像这样非常复杂的类型签名,特别是有很多联合时,这可能是一个迹象,表明你应该将其拆分为几个单独的方法定义。特别是,你可能至少想避免 foo(a, b, c=nothing),而更喜欢 foo(a, b, c)foo(a, b)。此外,请考虑类型之间是否存在联系,例如,当 bMatrix 时,a 是否仅为 Vector - DNF
我不确定您想要的具体内容,但类似以下格式: foo(a::Integer, b::Vector{<:Integer}) = ... end foo(a::Vector{<:Integer}, b::Matrix{<:Integer}, c::Vector{<:Integer}) = ... end foo(a::Integer, b...) = error("如果a是整数,则b必须为整数向量。") end foo(a::Vector{<:Integer}, b...) = error("如果a是整数向量,则b必须为整数矩阵,c必须为整数向量...") end我认为这实际上会更容易编写好的错误消息。 - DNF
我认为这样做对于我的当前问题没有好处。我不相信这会使我的代码更易读。 - user2329125
嗯,我非常确定它会(当你添加换行符时。事实上,我有99%的把握它会。)但是每个人都有自己的喜好。 - DNF
很抱歉一直在强调这个问题,但你基本上是在接受多重分派中困难和烦人的部分(正确获取复杂函数签名),同时拒绝了它的好处(避免显式输入类型检查、逻辑上不同方法的分离等)。在我看来,这是Julia的最大优势,所以你想通过编写非惯用代码来放弃它(以及可能的性能),这让我感到困惑。 - DNF
显示剩余6条评论
1个回答

7
是的,Array{UInt8} <: Array{Integer} == false。这被称为“参数不变性”。许多其他问题都讨论了这个主题。
你遇到的另一个问题是,当你使用静态函数参数时——也就是说,f(…) where T——T 必须匹配某些东西,因为它可以在函数体中使用。这在Union中会引起麻烦,因为T并不在每个选项中都可用。我相信有一个开放的问题涉及改变这种行为,以允许匹配不包含TUnion元素,这将把该绑定转换为未定义的变量,如果你尝试访问它的话。
目前的解决方法是使用类型变量,这些变量不是函数的静态参数。例如,
   foo(a::Union{Integer, Vector{<:Integer}},
       b::Union{Vector{<:Integer}, Matrix{<:Integer}},
       c::Union{Void, Vector{<:Integer}} = nothing) = 1

如果我没记错,在旧的版本中,当三角派遣不被支持时,我们必须定义一个typealias来解决这个问题,例如 typealias IntVector{T<:Integer} Vector{T}foo(x::Union{Integer, IntVector}) = x。很高兴看到现在不再需要这样做了! - Gnimuc

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接