我在访问某个字段时遇到类型不稳定的问题
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
知道如何才能让它发挥作用吗?
答案 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