The problem I’m trying to solve is to have a function f that:
- takes as as one of its arugments a Tuple of Symbols (of variable length), called
fields - for each
fieldinfields, looks up the type of that field in a constant, global mapping calledfield_to_type::Dict{Symbol, DataType} - does some processing / data loading to get the data corresponding to each
field - returns a NamedTuple whose field names are equal to
fields, and whose fields types and data correspond to the above.
Importantly, the return type of the function must be inferable (e.g.NamedTuple{(:foo, :bar), Tuple{Int64, Int64})). Thefieldsargument can always be specified as a constant directly at the call site, e.g.f((:foo, :bar)). If it would help, it could also be specified as aValtype.
In the rest of my program,fwill only be called at a small number of sites, with different values offieldsspecified at the call site.So far, I’ve managed to come up with a way of doing this using a@generatedfunction, but it is kind of hacky and hard to read:
const field_to_type = Dict(
:foo => String,
:bar => Bool,
:doo => Int64,
:dah => String,
:asd => Float64,
:dsa => String
)
@generated function f(::Val{fields}) where {fields}
values_expr = :(())
for f in fields
push!(values_expr.args, :(Array{$(field_to_type[f])}(undef, len)))
end
expr = quote
# Do the 'dynamic' computation in the function at call-time, here we pick a random integer
len = rand(1:10)
values = $(values_expr)
NamedTuple{$(fields)}(values)
end
return expr
end
f(Val((:asd, :dsa))) # correctly inferred as having type NamedTuple{(:asd, :dsa),Tuple{Array{Float64,1},Array{String,1}}}
The len and constructing of Arrays is simply representative of the ‘call-time’ computation.
Currently the main thing I don’t like about this is the fact the the Tuple expression has to be constructed at the top, then spliced in referring to variables in the quoted scope. I would much prefer it if the flow of the code could at least be linear.
Would really appreciate some feedback / help! This is my first time really messing around with this side of Julia, so I might be missing something very obvious or going about this the completely wrong way. Perhaps there is even a way to achieve something similar without any metaprogramming and just via the type system (though seems unlikely).