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
field
infields
, 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})
). Thefields
argument 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 aVal
type.
In the rest of my program,f
will only be called at a small number of sites, with different values offields
specified at the call site.So far, I’ve managed to come up with a way of doing this using a@generated
function, 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).