Reputation: 42214
I have created a complex computational model with lots of parameters. Since I need to run many scenarios, I have decided to wrap all those input parameters into one huge struct
:
using Parameters
@with_kw struct MyModel
a::Int = 5
b::Float64 = 5.5
c::Matrix{Float64} = rand(3,4)
# 40 other parameters go here
end
I have an object m
for an example:
m = MyModel(a=15)
Now when writing mathematical code I do not want to write m.
in front of each symbol. Hence I need to make struct fields into local variables. One way is to to use @unpack
macro:
@unpack a, b, c = m
For huge structs that I want to unpack in various functions this is just inconvenient (note that my struct has around 40 fields). How can I unpack the struct without spending time and cluttering my code with all those parameters?
Upvotes: 5
Views: 2421
Reputation: 31
I think there's a much more idiomatic approach that doesn't require macros or any other packages. The documentation suggests the most idiomatic approach, for instance the docs for Matrix factorization.
In particular, this line:
julia> l, u, p = lu(A); # destructuring via iteration
which suggests iteration is the right way to go. So all that is left is to make your struct implement the iteration interface
Your struct looks like this
struct MyModel
a::Int = 5
b::Float64 = 5.5
c::Matrix{Float64} = rand(3,4)
# 40 other parameters go here
end
To implement the iteration interface you need to define Base.iterate
which takes MyModel
as a parameter and a "state". That function returns the element that corresponds to the state, and calls the next iteration (sort of like a linked list). For example:
function Base.iterate(m::MyModel, state)
if state == 1
return(m.a, state+1)
elseif state == 2
return(m.b, state+1
elseif state == 3
return(m.c, state+1)
else
return nothing
end
end
In julia an iteration stops when next(iter) == nothing
, that's why you have to return nothing
when there is nothing left to iterate over.
You can find a much more idiomatic (but contrived) example in the source code for the lu
factorization:
# iteration for destructuring into components
Base.iterate(S::LU) = (S.L, Val(:U))
Base.iterate(S::LU, ::Val{:U}) = (S.U, Val(:p))
Base.iterate(S::LU, ::Val{:p}) = (S.p, Val(:done))
Base.iterate(S::LU, ::Val{:done}) = nothing
which uses Val
for some compile-time optimizations if I'm not mistaken.
In the particular case of a struct with 40 fields I can't think of a more ergonomic way, maybe the data is better suited for another storage option.
Upvotes: 1
Reputation: 42214
For custom cases, you can make a dedicated macro for your type, for other cases see the answers above:
macro unpack_MyModel(q)
code = Expr(:block, [ :($field = $q.$field) for field in fieldnames(MyModel) ]...)
esc(code)
end
This simply inserts the following code:
julia> @macroexpand @unpack_MyModel(m)
quote
a = m.a
b = m.b
c = m.c
end
This macro can be used inside of any function such as:
function f(m::MyModel)
@unpack_MyModel(m)
return a+b
end
Upvotes: 2
Reputation: 7674
Another option is StaticModules.jl. Here's an example that I copied and pasted from the README for that package:
julia> struct Bar
a
b
end
julia> @with Bar(1, 2) begin
a^2, b^2
end
(1, 4)
Upvotes: 2
Reputation: 2580
The macro @with_kw
from Parameters.jl defines a macro for this purpose:
julia> using Parameters
julia> @with_kw struct MyModel # exactly as in the question
a::Int = 5
b::Float64 = 5.5
c::Matrix{Float64} = rand(3,4)
# 40 other parameters go here
end
MyModel
julia> @macroexpand @unpack_MyModel x
quote
a = x.a
b = x.b
c = x.c
end
Thus writing @unpack_MyModel m
is equivalent to writing @unpack a, b, c = m
, when you know that m isa MyModel
.
Upvotes: 4