Hi all, I appreciate any help in making the following code type stable:
abstract type Something{T} end
Base.eltype(:: Something{T}) where T = T
mutable struct OneThing{T} <: Something{T}
α :: T
end
# HERE
mutable struct ManyThings{T} <: Something{T}
L :: Vector{Something}
end
# OR HERE
function ManyThings(L :: Vector{<: Something})
T = promote_type(eltype.(L)...)
ManyThings{T}(L)
end
function test()
A = OneThing(2.0)
B = OneThing(3)
S = ManyThings([A, B])
@code_warntype ManyThings([A, B])
end
test()
output:
┌ Info:
└ eltype(S) = Float64
Variables
#self#::Type{ManyThings}
L::Array{OneThing,1}
T::Any
Body::ManyThings{_A} where _A
1 ─ %1 = Base.broadcasted(Main.eltype, L)::Base.Broadcast.Broadcasted{Base.Broadcast.DefaultArrayStyle{1},Nothing,typeof(eltype),Tuple{Array{OneThing,1}}}
│ %2 = Base.materialize(%1)::Any
│ (T = Core._apply(Main.promote_type, %2))
│ %4 = Core.apply_type(Main.ManyThings, T)::Type{ManyThings{_A}} where _A
│ %5 = (%4)(L)::ManyThings{_A} where _A
└── return %5
Thanks in advance.
I am not sure what you want here: the L
field of a ManyThings
is not a concrete type, so how could the compiler infer more?
Also, note that eltype
should be defined on types, not values.
Vector{Something}
is not a concrete type, Vector{S} where S<:Something{T}
would be. That would require the change of ManyThings
parameters to ManyThings{T, S<:Something{T}}
.
Another question: why is ManyThings
mutable? Having a container as a single field sort of implies that you want to change the contents of said container, not re-bind L
to something else.
Thanks for the replies.
@Tamas_Papp, my objective is to change ManyThings
so that the compiler can infer more.
I changed eltype
to eltype(:: Type{<: Something{T}}) = where T = T
, is that the right way to do it?
@Vasily_Pisarev, there are two problems with Vector{S}
: I can’t have S <: Something{T}
because the element types are not the same (in the example, one is Float64
, the other is Int
). And if I only have S <: Something
, then S
is also not inferred. This is essentially the problem, I can’t have a concrete type for S
, since promote_type(OneThing{Float64}, OneThing{Int}) = OneThing
. Regarding mutable
, it was a mistake on my part.
I got the following to be type stable, but I can’t extend it:
struct TwoThings{T,S,Q} <: Something{T}
A :: S
B :: Q
end
function TwoThings(A :: Something, B:: Something)
T = promote_type(eltype(A), eltype(B))
TwoThings{T,typeof(A),typeof(B)}(A, B)
end
Yes, that’s how you define eltype
.
You may want something like
mutable struct ManyThings{T} <: Something{T}
L::Vector{Something{T}} # note T
end
but it is hard to be sure from your problem description. Cf
https://docs.julialang.org/en/v1/manual/performance-tips/#Avoid-fields-with-abstract-type-1
You can define a custom promote and conversion rules like
Base.promote_rule(::Type{OneThing{T}}, ::Type{OneThing{S}}) where {T,S} = OneThing{promote_type(T, S)}
Base.convert(::Type{OneThing{T}}, x::OneThing{S}) where {T,S} = OneThing{T}(x.α)
Base.convert(::Type{OneThing{T}}, x::OneThing{T}) where T = x
if that suits your use case.
Of course, that would put OneThing(3.0)
instead of the original OneThing(3)
into ManyThings
.
But once you put things into a container with an abstract element type, you lose the type stability, that’s inevitable.
Thanks, that makes sense. I think I’ll have to leave it type unstable, because my use case is a little more complicated (for instance, I have several other things <: Something{T}
, and there is no clear way to promote them). If that slows down the code too much, I’ll take a second look.