Abstract supertype or type union for a set of parametric types

Hello there,

I am a bit lost with when should I use Union or not in Julia 0.7/1.0+. In the following example, I would have used an abstract super type for TimeComponent. Should I use an Union as in the example. What would the pros and cons now?

(I could make a simpler example, but this one is actually not hard and exactly what I need)

using SparseArrays
using OrderedCollections

# Discrete time sets
struct Instants{T<:Real}
    timeset::Vector{T}
    timenodes::SparseVector{Bool}
    timelinks::SparseMatrix{Bool}
end

struct Steps{T<:Real}
    timeset::AbstractRange
    timenodes::SparseVector{Bool}
    timelinks::SparseMatrix{Bool}
end

# Continuous time set
struct Interval{T<:AbstractFloat} <: AbstractInterval
    start::Tuple{T,Bool}
    finish::Tuple{T,Bool}
end

struct TimeNodes{T<:AbstractFloat}
    time_node::OrderedDict{Interval{T}, Int}
    node_time::Dict{Int, Interval{T}}
end

struct TimeLinks{T<:AbstractFloat}
    time_link::OrderedDict{Interval{T}, Tuple{Int, Int}}
    link_time::Dict{Tuple{Int, Int}, Interval{T}}
end

struct Intervals{T<:AbstractFloat}
    timeset::Interval{T}
    timenodes::TimeNodes{T}
    timelinks::TimeLinks{T}
end

# Supertype time set
const TimeComponent = Union{Instants, Steps, Intervals}

Edit: added using for SparseArrays and OrderedCollections so the example is functional …

1 Like

I am not sure of performance difference, but one immediate difference that comes to mind is extensibility. Imagine AbstractArray was a union type. Anyone who wanted to extend it in their own package and define their own array type would have had to make a PR to Julia Base to add their type to the union there. So abstract types are more extensible by others. Also looking at a type that subtypes an abstract type, it is obvious which type it extends, no need to track down unions. On the other hand, you can have one concrete type part of many union supertypes, so that’s extra flexibility, but that may also be a sign that you could have arranged your types in a better hierarchy of abstract types. That’s my 2 cents anyways.

3 Likes

I agree with @mohamed82008: use abstract types to define type hierarchies. I would mostly use Unions for

  1. field types in composite/container types, especially mutable ones,
  2. for unifying some similar-looking code in dispatch.

You can examples for both in Base and the standard libraries.

BTW, in

the type parameter is redundant, and the type is not concrete as is.

1 Like

The nice thing about unions is you can use them to simulate multiple inheritance

Thanks for the answers. I will keep my habits of making an abstract supertype.

the type parameter is redundant, and the type is not concrete as is.

@Tamas_Papp Thanks, I was on my way of doing some changes and forgot the parameter there. It looks like that now (I think that is the recommended usage)

struct Steps{R<:AbstractRange}
    timeset::R
    timenodes::SparseVector{Bool}
    timelinks::SparseMatrix{Bool}
end

The nice thing about unions is you can use them to simulate multiple inheritance

@mkborregaard Thanks for the nice tip, quite useful actually