I’m writing some Julia code in which I need to loop over a collection of observations. The observations are not all of the same type and each type of observation is defined in a specific structure. I need to then perform a number of observation-specific calculations (these calculations do all return the same type). However, the loop over the observations is not type stable due to the fact we are indexing into a heterogeneous collection. Below is some example code illustrating the basic idea.
I’m wondering if others have encountered similar issues and found a better way to organise collections of heterogeneous data that can be looped over in a type-stable way.
# Some observation structures
abstract type Observation end
struct O1 <: Observation
# O1 Observation fields
end
struct O2 <: Observation
# O2 Observation fields
end
struct O3 <: Observation
# O3 Observation fields
end
# Main loop over observations
function observation_loop(d::Vector{<:Observation})
for oᵢ in d
# Perform some observation-specific calculation
r = observation_calculation(oᵢ)
println(r)
end
return nothing
end
# Observation-specific calculations
function observation_calculation(O::O1)
return "O1 Result"
end
function observation_calculation(O::O2)
return "O2 Result"
end
function observation_calculation(O::O3)
return "O3 Result"
end
# Some collection of observations
d = [O2(), O2(), O3(), O3(), O1(), O1(), O1()]
@code_warntype observation_loop(d)
The output of from @code_warntype for this example is:
MethodInstance for observation_loop(::Vector{Observation})
from observation_loop(d::Vector{<:Observation}) in Main at Untitled-1:10
Arguments
#self#::Core.Const(observation_loop)
d::Vector{Observation}
Locals
@_3::Union{Nothing, Tuple{Observation, Int64}}
oᵢ::Observation
r::String
Body::Nothing
1 ─ %1 = d::Vector{Observation}
│ (@_3 = Base.iterate(%1))
│ %3 = (@_3 === nothing)::Bool
│ %4 = Base.not_int(%3)::Bool
└── goto #4 if not %4
2 ┄ %6 = @_3::Tuple{Observation, Int64}
│ (oᵢ = Core.getfield(%6, 1))
│ %8 = Core.getfield(%6, 2)::Int64
│ (r = Main.observation_calculation(oᵢ))
│ Main.println(r)
│ (@_3 = Base.iterate(%1, %8))
│ %12 = (@_3 === nothing)::Bool
│ %13 = Base.not_int(%12)::Bool
└── goto #4 if not %13
3 ─ goto #2
4 ┄ return Main.nothing