I often work with large structs w/ many (over 20) parameters.
I currently do something like:
struct m; α; β; γ; end # store parameters in a struct
m(; α=0.1,β=0.2,γ=0.3) = m(α,β,γ) # fcn that returns struct w/ defaults
p = m() # e.g. store the param in `p`
# Example 1: no unpacking
f1(x; p=p) = x^(p.α) + x^(p.β) + x^(p.γ)
# Example 2: unpack w/ current version of Julia
function f2(x;p=p)
α, β, γ = p.α, p.β, p.γ
return x^(α) + x^(β) + x^(γ)
end
# Example 3: unpack w/ Julia 1.7+
function f3(x;p=p)
(; α, β, γ) = p
return x^(α) + x^(β) + x^(γ)
end
Is there a way to unpack the object p inside a function w/o repeating it’s components (; α, β, γ) = p?
I realize this sounds lazy, but when you have 20+ params in a struct & have to do (; α, β, γ) = p over many times…
I’d like something like (maybe a macro @unpack_$)
function f4(x;p=p)
@unpack_$ p
return x^(α) + x^(β) + x^(γ)
end
Weird shot in the dark. Maybe something like
fieldnames(typeof(p)) # returns tuple (:α, :β, :γ)
# Then have `@unpack_$ p` do something like:
fieldnames(typeof(p)) = p.α, p.β, p.γ
I was just about to reply with the same reference. I wish this feature could be reworked so that it is safer to use because I often need to extract many parameters from a struct.
If the parameters are related in some way it might be better to group some of them together into a few named tuples instead of 30 free-floating parameters.
Yes, I guess @with_kw lets the macro know about your definition. However, it might be worth it, since it allows the default parameter values to be defined at the same time, saving the need to define the extra constructor.
@with_kw struct M; α=1; β=1; γ=1; end; # no need for separate default constructor
I agree with @mauro3 to be cautious about this kind of unpacking. This was the much-derided with of Pascal (yes I am old, thanks), and also an anti-pattern in Javascript.
I often find myself in this situation. I use ComponentArrays.jl and UnPack.jl instead. Is there any advantage of this approach vs the one using structures? What’s best practice?
No one will ever be confused, and confusion wastes much more time than typing. It also means you can use longer names in your struct and symbols in the actual math, which is self-documenting.
I dont know, I think new people could be confused by @unpack, and they’'re the ones we dont want to confuse.
Early on I put macros like that everywhere in my code, but when it came to onboarding new people to shared codebase in an organisation it seemed much better to remove them all again - it made things easier to understand if we just used base julia. Especially in the context of code and models that were complicated enough already.
Edit: not sure why I deleted the last post, but here it was:
The unpacking idiom is sufficiently common that people shouldn’t be confused. @unpack α, β, γ = p is all over the place, and so will (; α, β, γ) = p before too long. But the problem with your code is one of defensive programming: α, β, γ = p.α, p.β, p.γ is extremely error prone. If you accidentally get them out of order, maybe after adding a new parameter or removing one, then you will get all sorts of silent bugs. If you have to copy/paste these between funcitons then the probability for a mistake increases further.
Users have to learn some new idioms any time to learn a new language. This is one to learn as early as possible because the alternatives have been so error prone for everyone. But in general, I agree. Other macros I am very suspicious of for new users.
And this isn’t even a macro anymore: (; a, b) = p. They will have to learn about named tuples pretty quickly, and this shiould be a part of that training.
If other people will look at the code, I’d prefer to at least give a hint with
(; a, b) = p # unpacking fields
It is cute syntactic sugar, but not very readable for the uninitiated. With @unpack at least it’s clear and someone can look up the documentation easily.
For me the beauty of Julia is that it is almost as readable as pseudo-code. I sometimes brag about the readability, only for someone to glance at it and get tripped up on something not readable. Examples include short-circuit && instead of if, and array undef.
The named tuple idiom is better as it’s in base and mirrors how named tuples splat to keyword arguments. But I guess there is some taste involved here too!
I specifically commented as I’m just about to publish a paper with similar unpacking code in inline examples. There is already too much to explain for non-julia users (symbols, type parameters, do blocks, etc…), and although it’s simple, an @unpack macro does add one more thing to the list, including what a macro is at all.
That’s precisely what drove me to ComponentArrays. It is also very tedious to change the code to try different model pieces that take subsets of a large vector of parameters.
No, @unpack is a macro and expands essentially to the same code. Have checked for SimpleUnPack.@unpack and Parameters.@unpack and all of these functions reduce to the same code, i.e., at the level of @code_typed:
function unpack_1(p)
Parameters.@unpack α, β, γ = p
α + β + γ
end
function unpack_2(p)
SimpleUnPack.@unpack α, β, γ = p
α + β + γ
end
function unpack_3(p)
α, β, γ = p.α, p.β, p.γ
α + β + γ
end
Thanks a ton! I like that approach, thanks for showing me.
I ended up simply just doing it in my particular code and it didn’t seem to have an impact so I stuck to using @unpack since it made syntax much cleaner