Hi all, I am developing some simulation code that can be used either to do a quick calculation or a more complete and slower algorithm. I implemented two functions that either produce a “simple” or “complex” structure; for the sake of simplicity, let’s assume that the calculations are very trivial:
abstract type Results{T <: Number} end
struct SimpleResults{T} <: Results{T}
val1::T
val2::T
end
simple(input) = SimpleResults(input, input * 2)
struct ComplexResults{T} <: Results{T}
val1::T
val2::T
val3::T
end
complex(input) = ComplexResults(input, input * 2, input * 3)
Both return types are derived from the abstract type Results
.
In a typical case, these calculations should be performed many times in a row, producing a long vector of results. For these, I implemented a ListOfResults
type that basically wraps a AbstractVector{<: Results}
and keeps some ancillary information too (in info
):
struct ListOfResults{R <: Results, V <: AbstractVector{R}}
vec::V
info::String
end
I am now a bit puzzled in thinking how to create a proper API for the function that creates a ListOfResults
object. I would like to let the user decide whether simple
or complex
should be called and which vector-like use for ListOfResults.vec
(Vector
, CuArray
, …).
Since the exact eltype
of vec
is decided only after a call to simple
or complex
is done, my strategy would be something like this:
function fill_list_of_results(list_of_inputs, array_type, fn_to_call)
# Query the first result
first_result = fn_to_call(list_of_inputs[begin])
# Allocate the array
R = typeof(first_result)
V = array_type{R}
vec = V(undef, length(list_of_inputs))
# Build the object to be returned
result = ListOfResults(vec, "some useful information")
# Fill the array
result.vec[begin] = first_result
for (cur_idx, cur_input) in enumerate(list_of_inputs)
(cur_idx == firstindex(list_of_inputs)) && continue
result.vec[cur_idx] = fn_to_call(cur_input)
end
return result
end
This implementation works as expected:
# Works!
fill_list_of_results([1, 2, 3], Vector, simple)
# Works too!
fill_list_of_results([1.0, 2.0, 3.0], Vector, complex)
However, I am not entirely satisfied with it. I don’t find the implementation of fill_list_of_results
too elegant nor “Julia”-like: it calls the function once, saves the result back and uses the type to build an array, then does a for
loop skipping the first element… It’s more complicated than I would have expected, but I am at loss to find a smarter way to do the same. Perhaps I should completely revise the way I’m tackling the problem?
I would like to hear your opinion: is there a cleaner way to do what I need? Thank you!