从抽象类型

时间:2018-03-29 17:20:43

标签: polymorphism julia multiple-dispatch

我在访问某个字段时遇到类型不稳定的问题 abstract type(在朱莉娅v0.6中)。

假设我有一个类型层次结构,所有这些都共享相同的实例变量。 我知道它是类型不稳定的,并且不保证访问是正确的 该字段正确,因为有人总是可以定义缺少的新子类型 预期变量。但是,即使将成员访问包装在一个 功能,访问仍然是类型不稳定,我无法弄清楚原因。

假设我们有一个简单的类型层次结构:

julia> begin
       abstract type AT end
       mutable struct T1 <: AT
         x::Int
       end
       mutable struct T2 <: AT
         x::Int
       end
       end

我们不是直接访问a.x,而是将其包装在功能障碍中:

julia> getX(a::AT)::Int = a.x
>> getX (generic function with 1 method)

julia> @code_warntype getX(T1(1))
Variables:
  #self# <optimized out>
  a::T1

Body:
  begin
      return (Core.getfield)(a::T1, :x)::Int64
  end::Int64

请注意,通过此方法的访问是类型稳定的,因为它可以推断出 a的类型为T1

但是,当我在编译器无法知道类型的上下文中使用getX时 在变量提前,它仍然是类型不稳定的:

julia> foo() = getX(rand([T1(1),T2(2)]))
>> foo (generic function with 1 method)

julia> @code_warntype foo()
Variables:
  #self# <optimized out>
  T <optimized out>

Body:
  begin
      SSAValue(0) = (Core.tuple)($(Expr(:new, :(Main.T1), 1)), $(Expr(:new, :(Main.T2), 2)))::Tuple{T1,T2}
      SSAValue(2) = $(Expr(:invoke, MethodInstance for rand(::Array{AT,1}), :(Main.rand), :($(Expr(:invoke, MethodInstance for copy!(::Array{AT,1}, ::Tuple{T1,T2}), :(Base.copy!), :($(Expr(:foreigncall, :(:jl_alloc_array_1d), Array{AT,1}, svec(Any, Int64), Array{AT,1}, 0, 2, 0))), SSAValue(0))))))
      return (Core.typeassert)((Base.convert)(Main.Int, (Core.getfield)(SSAValue(2), :x)::Any)::Any, Main.Int)::Int64
  end::Int64

请注意,它内联getX的主体,并基本上替换它 tmp.x::Int64。这令我感到惊讶,因为我期待getX发送给我 我们在上面看到的相同定义的两个实例之一,其中没有断言 因为这种类型是已知的,所以是必要的。

我认为如果getX 实际只是定义的话,这确实有意义 对于抽象基类型AT - 没有方法可以调度到 我想象的方式。所以我尝试重新定义getX以便生成它 每个子类型的具体方法如下:

julia> getX(a::T where T<:AT)::Int = a.x
>> getX (generic function with 1 method)

但这实际上是一个相同的定义,并没有改变:

julia> methods(getX)
>> # 1 method for generic function "getX":
getX(a::AT) in Main at none:1

知道如何才能让它发挥作用吗?

2 个答案:

答案 0 :(得分:3)

如果您定义getX以获取AT的子类型,那么您就拥有foo的类型稳定代码:

julia> function getX(a::T)::Int where T <: AT
           a.x
       end
getX (generic function with 1 method)

julia> foo() = getX(rand([T1(1),T2(2)]))
foo (generic function with 1 method)

julia> @code_warntype foo()
Variables:
  #self# <optimized out>
  T <optimized out>

Body:
  begin
      SSAValue(0) = (Core.tuple)($(Expr(:new, :(Main.T1), 1)), $(Expr(:new, :(Main.T2), 2)))::Tuple{T1,T2}
      return (Main.getX)($(Expr(:invoke, MethodInstance for rand(::Array{AT,1}), :(Main.rand), :($(Expr(:invoke, MethodInstance for copy!(::Array{AT,1}, ::Tuple{T1,T2}), :(Base.copy!), :($(Expr(:foreigncall, :(:jl_alloc_array_1d), Array{AT,1}, svec(Any, Int64), Array{AT,1}, 0, 2, 0))), SSAValue(0)))))))::Int64
  end::Int64

我不知道为什么会这样,也许对这个问题有更多了解的人会对此有所了解。 此外,这只是类型稳定的,因为getX可以保证返回Int。如果您没有强制getX执行此操作,则无法保证返回Int,因为您可能拥有包含非Ints的不同AT子类型。

答案 1 :(得分:0)

啊,所以我需要自己手动定义getX的不同版本。

我将julia的类型调度机制与C++的模板混合在一起 实例。我认为错误地想象 julia定义了新版本 对于每个调用它的getX T,与C ++模板的方式相同 机构。

在这种情况下,当我说

时,我几乎是正确的
  

我期待getX派遣到两个实例中的一个[...]   因为类型是已知的,所以不需要断言。

然而,在这种情况下,实际上并没有两种不同的方法 派遣到 - 只有一个。如果我实际定义两个不同的 方法,调度机制可以满足类型稳定性:

julia> begin
       abstract type AT end
       mutable struct T1 <: AT
         x::Int
       end
       mutable struct T2 <: AT
         x::Int
       end
       end

julia> getX(a::T1) = a.x
>> getX (generic function with 1 method)

julia> getX(a::T2) = a.x
>> getX (generic function with 2 methods)

julia> foo() = getX(rand([T1(1),T2(2)]))
>> foo (generic function with 1 method)

julia> @code_warntype foo()
Variables:
  #self# <optimized out>
  T <optimized out>

Body:
  begin
      SSAValue(0) = (Core.tuple)($(Expr(:new, :(Main.T1), 1)), $(Expr(:new, :(Main.T2), 2)))::Tuple{T1,T2
}
      return (Main.getX)($(Expr(:invoke, MethodInstance for rand(::Array{AT,1}), :(Main.rand), :($(Expr(:invoke, MethodInstance for copy!(::Array{AT,1}, ::Tuple{T1,T2}), :(Base.copy!), :($(Expr(:foreigncall, :(:jl_alloc_array_1d), Array{AT,1}, svec(Any, Int64), Array{AT,1}, 0, 2, 0))), SSAValue(0)))))))::Int64
  end::Int64

我找到了解决这个问题的灵感: https://stackoverflow.com/a/40223936/751061