I’m writing an application with a whole bunch of vectors of length N that syntactically have elements having a consistent meaning (chemical components in a mixture). Most of the time, I don’t need the names, but on some occasions (such as chemical reactions) I actually do need the names. This means I need to build an indexer to make the code readable.
For example, let “components” be a vector containing mass flows and “idx” be an indexer
stack.components[idx[:CO2]] = MolecularWeight[idx[:CO2]] * rxnCO2
If I set “idx” to be a NamedTuple global constant, I can’t change it because changing it would change the type. If I set it to be a Dictionary, I could mutate it and the program would adjust. This is a low level operation that could affect performance so using Dictionaries might take a performance hit. Now the thing is, this component indexer should change VERY infrequently, but it might be too inconvenient to reload the whole application if I wanted to change the indexer.
Then I discovered generated functions. One interesting property of generated functions is that if you make them observe a global variable, they won’t update if the variable changes. However, if you REDEFINE the generated function after changing the global variable, it will update. Redefining this generated function ends up recompiling all functions that use it. This means I could write:
function rename_indexer(names::NTuple{N, Symbol}) where N
global INDEX_LIST
INDEX_LIST = names
include(joinpath(@__DIR__, "Indexer.jl"))
end
The code that I would stick into “Indexer.jl” is
@generated function build_indexer()
global INDEX_LIST
ind = 1:length(INDEX_LIST)
ex = Expr(:call)
push!(ex.args, Expr(:curly, :NamedTuple, :($INDEX_LIST)))
push!(ex.args, Expr(:tuple, ind...))
return ex
end
This code produces a NamedTuple which is explicitly defined in code, and should produce a constant propagation.
Thus by invoking “rename_indexer” I would rebuild that generated function which would cause recompiling only the functions that use this indexer function (which produces a new constant NamedTuple), allowing for performant run-time execution that can periodically update. Correct? Do any of you see any pitfalls of this approach?