Thanks everyone for the replies.
Jiahao: Thanks especially for the pointer to MAX_TYPE_DEPTH. This is
exactly what I hit, and now I'm trying to learn what its consequences are.
I appreciate the need for constraints to keep type inference efficient, but
I'm not sure why what I am doing is running up against them. The root
problem seems to be at the level of T5's constructor. With the same type
hierarchy as in the original post, constructing a T4 is not a problem.
*julia> **@code_warntype T4(T3(T2(T1{Float64}())))*
Variables:
x::T3{T2{T1{Float64}}}
Body:
begin
return $(Expr(:new, T4{T3{T2{T1{Float64}}}},
:(x::T3{T2{T1{Float64}}})))
end::T4{T3{T2{T1{Float64}}}}
But constructing a T5 is a problem, even if I tell the compiler exactly
what kind of T5 I am constructing.
*julia> **t4 = T4(T3(T2(T1{Float64}())));*
*julia> **@code_warntype T5(t4)*
Variables:
x::T4{T3{T2{T1{Float64}}}}
Body:
begin
return ((top(apply_type))(T5,T)
*::Type{_<:T5{T<:T4{T<:T3{T<:T2{T<:T1{I<:Number}}}}}}*
)(x::T4{T3{T2{T1{Float64}}}})*::T5{T<:T4{T<:T3{T<:T2{T<:T1{I<:Number}}}}}*
end*::T5{T<:T4{T<:T3{T<:T2{T<:T1{I<:Number}}}}}*
*julia> **@code_warntype T5{T4{T3{T2{T1{Float64}}}}}(t4)*
Variables:
#s2::Type{T5{T4{T3{T2{T1{Float64}}}}}}
x::T4{T3{T2{T1{Float64}}}}
Body:
begin
return $(Expr(:new,
:((top(apply_type))((top(getfield))(Main,:T5)::Type{T5{T<:T4{T<:T3{T<:T2{T<:T1{I<:Number}}}}}},T)::Type{_<:T5{T<:T4{T<:T3{T<:T2{T<:T1{I<:Number}}}}}}),
:(x::T4{T3{T2{T1{Float64}}}})))
end*::T5{T<:T4{T<:T3{T<:T2{T<:T1{I<:Number}}}}}*
I would have thought in this last case there was nothing left to infer. Is
this last case expected behavior? Is there a way to construct a T5 such
that the type is known to the type inference system? (Without bumping up
MAX_TYPE_DEPTH; I'd like to learn to live within the default limits!)
Without delving too far into inference.jl, I had worried that maybe type
inference just punts when it encounters types exceeding MAX_TYPE_DEPTH.
But that seems to be not true. The functions I've attempted seems to
reason along happily about T5's once they are constructed. Here's a
trivial example.
*julia> **function id(x::T5) x end*
*id (generic function with 1 method)*
*julia> **t5 = T5(t4);*
*julia> **@code_warntype id(t5)*
Variables:
x::T5{T4{T3{T2{T1{Float64}}}}}
Body:
begin # none, line 1:
return x::T5{T4{T3{T2{T1{Float64}}}}}
end::T5{T4{T3{T2{T1{Float64}}}}}
More interesting examples require modifying the types in the hierarchy so
that they are more complex, but the few things I've tried (without pushing
too hard) seem ok, once a T5 is in hand.
Anyway, in practice I find I use the idiom
abstract AbstractClass
type C{A<:AbstractClass}
x::A
more fields....
end
so that the explicit type of field x is known if the type of C is known.
But if I understand the above correctly, it seems that C's constructor is
not type stable, in the sense that there are constructors for kinds of C's
such that the return type is not known to the type system, even when all
the argument types to the constructor are known. Is this correct?
Many thanks,
David