I am designing a large model that has a lot of parameters that will be imported from a file.
I can load the file in a dictionary and I would like to define the variables corresponding to each one of these parameter where the name of the variable would be the key and the assigned value the vale.
For example, this work in the global scope:
d = Dict("a" => 1, "b" => 2)
for param in d
@eval $(Symbol(param[1])) = @eval $(param[2])
end
This is convenient as I can then do
x = a + b
instead of
x = d["a"] + d["b"]
This is convenient because it makes the code more readable. Unfortunately, I think that this does only work in the global scope. Is there a way to do it inside a function?
Dynamically creating variable names is not considered good programming practice (in any language, not just Julia). It will make code *less * readable, not more.
If you don’t know the symbol names beforehand, you should use the dictionary. If you do know them, just write
My take on this (if the set of parameters is known beforehand) would be to convert the dict to a structure. Then, with Parameters, you can “unpack” the variables needed for specific functions to clean up the code:
julia> using Parameters
julia> @with_kw struct Params
a::Int
b::Int
end
Params
julia> d = Dict("a" => 1, "b" => 2)
Dict{String, Int64} with 2 entries:
"b" => 2
"a" => 1
julia> pars = Params(a = d["a"], b = d["b"])
Params
a: Int64 1
b: Int64 2
julia> pars.a + pars.b
3
julia> @unpack a, b = pars
Params
a: Int64 1
b: Int64 2
julia> a + b
3
Note that this does work but it’s a bit subtle why it works. In particular, the include does not create local variables in the body of readparams, rather it creates global variables called a and b which are then used in the computation of a + b at the end of the function. If you evaluate the code, you’ll see that a and b are globals afterwards:
julia> function readparams()
include("params.jl")
a + b
end
readparams (generic function with 1 method)
julia> readparams()
3
julia> a
1
julia> b
2
In Julia you cannot (intentionally) do include or eval into a local scope. If you want something like that you can instead generate a function definition based on the code, evaluate that definition and then call it (as many times as you want), like this (in a fresh REPL session so that a and b aren’t defined):
julia> @eval function useparams()
$(Meta.parseall(read("params.jl", String)).args...)
a + b
end
useparams (generic function with 1 method)
julia> useparams()
3
julia> a
ERROR: UndefVarError: a not defined
julia> b
ERROR: UndefVarError: b not defined
What’s the difference? The performance is quite different for one:
This is because readparams opens, parses and evaluates the contents of params.jl every time you call it and then evaluates a + b based on the new global values of a and b. This is quite slow. On the other hand useparams was generated with specific values of a and b and then when you call it, that fixed code is evaluated and since a and b are in the function body all it does is return the constant value 3:
julia> @code_llvm useparams()
; @ REPL[5]:1 within `useparams`
define i64 @julia_useparams_274() #0 {
top:
ret i64 3
}
Which do you want? It depends. Which behavior to you need? And do you care about the usage of the values you read from the file being fast or not? If you do want to use them and have it be fast, then you need to take a staged approach where you read the values/code from a file, generate code from that, compile that code and then call it as many times as you need to.