Metaprogramming - Unpack/pack struct-fields based on symbols

Hi everyone,

I would like pack/unpack (I am using the excellent Parameters package macros for this) specific fields of a struct within a function. I created example code here:

using Parameters
using Distributions

#construct example struct
mutable struct Model{E<:VariateForm, F<:VariateForm}
    α       ::  Vector{<:Distribution{E}}    # Transition
    β       ::  Vector{<:Distribution{F}}    # Observation
end
#test
model = Model( [Categorical([.5,.5]), Categorical([.5,.5])], [Normal(), Normal()])
info = fieldnames(Model)

function update!(model::Model, info) #Not Working
################# Unpack all model parameter that are in info field
#Not Working
    for i in eachindex(info)
        @unpack @eval $(info[i])  = model
    end

################# #Do something - just exemplary here to get different output
#Working
@eval $(info[1]) = [Normal(1,2), Gamma(1,2)]
@eval $(info[2]) = [Poisson(1), Normal(3,4)]

################# Pack all model parameter that are in info field
#Not Working
for i in eachindex(info)
    @pack! model = @eval $(info[i])
end

end

Question 1: I would like to unpack all the fields that come from a specific tuple but calling both
@unpack and @eval simultaneously does not seem to work, even though calling both individually works. Does anyone know a solution if I would be forced to call 2 macros simultaneously?

Question 2: On a related note, does anyone have a method to just unpack all parameter of a general struct?

Best regards,

Are you aware of @evals limitations? For instance that it will evaluate in global scope (i.e. module scope) even when called inside a function?

For gymnastics like you do, you should consider using a Dict where you can access the keys (i.e. your variables) and the values.

Q2: Did you consider using the @with_kw macro of Parameters.jl and the using @unpack_MyStruct?

Q1: @unpack needs on its left hand side the names of the variable to unpack, as syntax (macros only work on syntax)

2 Likes

Thank you for your answer! I saw in the docs that one may use the @unpack_* for @with_kw structs, I was wondering if it might also be possible for general containers.

I read that @eval will evaluate in the global scope, but so far it was the only way for me to call and assign values to symbols like this:

@eval $(info[1]) = [Normal(1,2), Gamma(1,2)]

I dont really understand why dicts would be better in this case as I would also only collect the symbols from there. The Model struct is quite a bit bigger in the real code and I perform many operations on it. I can always dispatch update! on different subtypes of it, but if more Models become available I ideally would have a more general update! function where I dont need to more and more code to it but instead generically updates based on some information.

No, this cannot be achieved with a macro as this only has information on the syntax. When a macro sees a, it only sees :a and not its contents (the macro is evaluated before a gets its contents). Thus it could not know what fields a has to unpack.

Using evals, in particular in local scopes such as functions, is considered bad style. Further it makes Julia slow as it cannot reason about types anymore (see performance section, about global variables applies here as well). Another reason is that you can get name-clashes, say your info contains a field which is identical to a global variable of yours, bam:

module A
cabbage(x) = 2 # so cabbage is a global
function f(inp)
  @eval $inp = 7
  return nothing
end
end
using .A
A.f(:lettuce) # ok
A.f(:cabbage) # error: ERROR: invalid redefinition of constant cabbage

So, your info structs interact in a non-trivial way with the rest of your code, changing either can lead to breakage.

Anyway, your line would read:

opts = Dict{Symbol,Any}()
opts[info[1]] = [Normal(1,2), Gamma(1,2)]

which looks nicer too and equally addresses “but instead generically updates based on some information.”

1 Like

Thanks for your explanation!

So, afaik, instead of using macros I could build a Dict with they info keys and some values, and then loop through a mutable struct to update information, and to avoid ambiguity?

1 Like

Yes, that sounds ok.