Dispatch on fields of my struct?

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:

  1. Replace T with Any, potentially incurring a performance hit.
  2. 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}).

1 Like

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

Please someone let me know if this is wrong!

1 Like

I am not sure if I understood the problem, what is holding you back?

julia> struct A{T} 
           data::Vector{T} 
       end

julia> struct B{T}
           data::Vector{T}
       end 

julia> a = A([1, 2, 3])
A{Int64}([1, 2, 3])

julia> b = B([:a, :b, :c])
B{Symbol}([:a, :b, :c])

julia> c = A([a, b])
A{Any}(Any[A{Int64}([1, 2, 3]), B{Symbol}([:a, :b, :c])])

julia> c = A{Union{A, B}}([a, b])
A{Union{A, B}}(Union{A, B}[A{Int64}([1, 2, 3]), B{Symbol}([:a, :b, :c])])

julia> c = A{Union{A{Int64}, B{Symbol}}}([a, b])
A{Union{A{Int64}, B{Symbol}}}(Union{A{Int64}, B{Symbol}}[A{Int64}([1, 2, 3]), B{Symbol}([:a, :b, :c])])

You can define the structure with the types as tight as you want.

@jling @Henrique_Becker Correct me if I’m wrong, but these seem to violate one of the Julia performance tips, which is the reason I rejected my option #1 above. I was hoping for a solution using only concrete types.

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.

1 Like