Most elegant way to create a vector of results if the result is an unknown type?

Maurizio_Tomasi
Thu, 26 Sep 2024 10:07:31 +0000
General Usage

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!

8 posts - 7 participants

Read full topic