Let’s say I have a loooong Parameters structure and a function where I want to refer simply with the parameter names rather than something like p.parameterx, how can I “automatically” copy the struct fields as local parameters ?
This doesn’t work because of course eval happens at global scope:
@kwdef mutable struct Parameters
a::Float64 = 0.1
b::String = "hello"
end
function bar()
p = Parameters()
# create local variables a == 0.1 and b= "hello"
fields = fieldnames(Parameters)
for f in fields
println(getproperty(p, f))
eval(:(local $f = $(getproperty(p, f))))
end
println("$b $a !")
end
hmm… it seems it’s not possible, the trick seems to eventually eval the whole function and then call the function, but the whole point it to make the code more readable also to people that doesn’t know Julia…
Provided the type definition was already evaluated and the type’s name is inlined, a macro call in the method body can automatically generate variables from the field names. An attempt:
macro fields2vars(t::Symbol, x)
type = Core.eval(__module__, t)
if !isstructtype(type)
throw(ArgumentError("@fields2vars only takes struct types, not $type."))
end
esc(:( (; $(fieldnames(type)...)) = $x::$type ))
end
function bar2()
p = Parameters()
@fields2vars Parameters p
println("$b $a !")
end
But since you’ll have to manually type the variables after the macro call to use them anyway, typing each used variable 1 more time doesn’t seem much more effort than this macro call, and the macro call constrains the code more because it needs a specific inlined type by definition-time to generate the local variables. In practice, you may not know the type even by compile-time, and property destructuring can be attempted at runtime.
Thank you… it works in my user-case, where I have compiled struct with default values but want let users the option to override them:
@kwdef mutable struct Parameters
a::Float64 = 0.1
b::String = "hello"
end
macro fields2vars(t::Symbol, x)
type = Core.eval(__module__, t)
if !isstructtype(type)
throw(ArgumentError("@fieldvars only takes struct types, not $type."))
end
esc(:( (; $(fieldnames(type)...)) = $x::$type ))
end
function bar2(;kwargs...)
p = Parameters()
# here keyword arguments are used to override the default values...
for (kw,kwv) in kwargs
found = false
if kw in fieldnames(Parameters)
setproperty!(p,kw,kwv)
found = true
end
found || error("Keyword \"$kw\" is not a valid parameter.")
end
@fields2vars Parameters p
println("$b $a !")
end
bar2()
bar2(a=0.2)
You don’t need that mutating for-loop, @kwdef is designed to allow you to instantiate with compatible keyword arguments and input types, e.g. p = Parameters(;kwargs...). The only practical difference is the custom error message for incompatible keywords being specific to bar2 rather than the internal Parameters, and if you want to keep that, you can check issubset(keys(kwargs), fieldnames(Parameters)) before you instantiate at all.