I’ve made a newbie mistake in constructing my types, and hoping for some good advice how to fix.

I two types, both containers having identical data layouts as below. I made them distinct because they represent different concepts, and need to behave differently with my local methods.

struct A{T}
data::Vector{T}
end
struct B{T}
data::Vector{T}
end
func(a::A{T}) where {T}= ...
func(b::B{T}) where {T}= ...

Only after constructing the types did I remember that they need to be recursive: data should be able to hold elements of either type A or B, e.g.

a= A([3])
b= B([4])
c= B([a,b])

which is impossible because the types are parametric. What should I do?

I see 2 options:

Replace T with Any, potentially incurring a performance hit.

Consolidate A and B under the same struct, but add another field to record their respective concepts as Symbols, and then dispatch on that field using Value types, e.g.

struct C{T}
data::Vector{T}
flavor::Symbol
end

where flavor can be :A or :B. But his requires to modify the methods, e.g.

func(c::C{T}) where {T}= func(c.flavor, c)
func(::Var{:A}, c::C{T}) where {T}= ...
func(::Var{:B}, c::C{T}) where {T}= ...

#1 is bad for performance; #2 feels a bit hacky, like I’m somehow evading Julia’s type-based dispatch system by instead trying to dispatch on fields.

Is there a third option? What would YOU DO? Thanks!

julia> abstract type AorB{T} end
julia> struct A{T} <: AorB{T}
data::Vector{T}
end
julia> struct B{T} <: AorB{T}
data::Vector{T}
end
julia> a= A([3])
A{Int64}([3])
julia> b= B([4])
B{Int64}([4])
julia> c= B([a,b])
B{AorB{Int64}}(AorB{Int64}[A{Int64}([3]), B{Int64}([4])])

do they conceptually fit under the same Abstract type? So the T here would indicate what prime data type is internally used (think of Complex{Float64} vs. Complex{Int64}).

The syntax here really trips me up. But this should be right.

julia> struct A{T}
x::T
end;
julia> struct B{T}
x::T
end;
julia> struct F{T<:Union{<:A, <:B}}
x::Vector{T}
end;
julia> function f(x::F{<:B})
println("a F of B type")
end;
julia> function f(x::F{<:A})
println("a F of A type")
end;
julia> fa = F([A(i) for i in 1:5])
F{A{Int64}}(A{Int64}[A{Int64}(1), A{Int64}(2), A{Int64}(3), A{Int64}(4), A{Int64}(5)])
julia> fb = F([B(i) for i in 1:5])
F{B{Int64}}(B{Int64}[B{Int64}(1), B{Int64}(2), B{Int64}(3), B{Int64}(4), B{Int64}(5)])
julia> f(fa)
a F of A type
julia> f(fb)
a F of B type

You want a solution that uses only concrete types to having elements of two distinct types mixed inside a Vector? I do not believe this is possible. I do believe my solution (specially the one that fully specifies A and B types) at least benefits from the union splitting optimization.