# Parsimonious way to unpack big structs

**URL:** <https://discourse.julialang.org/t/parsimonious-way-to-unpack-big-structs/66522>\
**Category:** General Usage\
**Tags:** struct\
**Created:** [August 17, 2021, 12:02am UTC](https://discourse.julialang.org/t/parsimonious-way-to-unpack-big-structs/66522 "2021-08-17T00:02:34Z")\
**Posts on this page:** 19\
**Page:** 1

<div class="post-metadata">

**Author:** ![Albert\_Zevelev](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/albert_zevelev/32/11844_2.png) [@Albert\_Zevelev](https://discourse.julialang.org/u/Albert_Zevelev)\
**Post date:** [August 17, 2021, 12:02am UTC](https://discourse.julialang.org/t/parsimonious-way-to-unpack-big-structs/66522/1 "2021-08-17T00:02:34Z")

</div>

I often work with large structs w/ many (over 20) parameters.  
I currently do something like:

```julia
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_$`)

```julia
function f4(x;p=p)
    @unpack_$ p
    return x^(α) + x^(β) + x^(γ)
end

```

Weird shot in the dark. Maybe something like

```julia
fieldnames(typeof(p)) # returns tuple (:α, :β, :γ)
# Then have `@unpack_$ p` do something like: 
fieldnames(typeof(p)) = p.α, p.β, p.γ

```

---

<div class="post-metadata">

**Author:** ![apo383](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/apo383/32/11272_2.png) [@apo383](https://discourse.julialang.org/u/apo383)\
**Post date:** [August 17, 2021, 12:32am UTC](https://discourse.julialang.org/t/parsimonious-way-to-unpack-big-structs/66522/2 "2021-08-17T00:32:18Z")

</div>

Well, there is the [“somewhat dangerous” unpacking](https://mauro3.github.io/Parameters.jl/stable/manual/#The-type-specific-(un)pack-all-macros-(somewhat-dangerous)) available from `Parameters.jl`. From the documentation,

```julia
function fn(var, pa::Para)
    @unpack_Para pa # the macro is constructed during the @with_kw
                    # and called @unpack_*
    out = var + a + b
end

```

where `a` and `b` are fields unpacked from `Para`. They admonish to be careful with this, as it can collide/shadow other variables.

---

<div class="post-metadata">

**Author:** ![Christopher\_Fisher](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/christopher_fisher/32/26132_2.png) [@Christopher\_Fisher](https://discourse.julialang.org/u/Christopher_Fisher)\
**Post date:** [August 17, 2021, 12:38am UTC](https://discourse.julialang.org/t/parsimonious-way-to-unpack-big-structs/66522/3 "2021-08-17T00:38:29Z")

</div>

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.

---

<div class="post-metadata">

**Author:** ![jzr](https://avatars.discourse-cdn.com/v4/letter/j/eb9ed0/32.png) [@jzr](https://discourse.julialang.org/u/jzr)\
**Post date:** [August 17, 2021, 12:39am UTC](https://discourse.julialang.org/t/parsimonious-way-to-unpack-big-structs/66522/4 "2021-08-17T00:39:04Z")

</div>

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.

---

<div class="post-metadata">

**Author:** ![Albert\_Zevelev](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/albert_zevelev/32/11844_2.png) [@Albert\_Zevelev](https://discourse.julialang.org/u/Albert_Zevelev)\
**Post date:** [August 17, 2021, 1:05am UTC](https://discourse.julialang.org/t/parsimonious-way-to-unpack-big-structs/66522/5 "2021-08-17T01:05:57Z")

</div>

@apo383

1. Happy Birthday!
2. it works, but only w/ `@with_kw struct`

```julia
julia> struct m; α; β; γ; end
julia> m(;α=0.1,β=0.2,γ=0.3) = m(α,β,γ);
julia> p = m();

julia> f1(x; p=p) = x^(p.α) + x^(p.β) + x^(p.γ);
julia> function f2(x;p=p)
           α, β, γ = p.α, p.β, p.γ
           return x^(α) + x^(β) + x^(γ)
       end;

julia> using Parameters;
julia> @with_kw struct M; α; β; γ; end;
julia> M(;α=0.1,β=0.2,γ=0.3) = M(α,β,γ);
julia> p=M();
julia> function f4(x; p::M=p)
           @unpack_M p
           return x^(α) + x^(β) + x^(γ)
       end;

julia> f1.(1:4)== f2.(1:4) == f4.(1:4)
true

```

If anyone has, other (better) ways, I’m all ears

---

<div class="post-metadata">

**Author:** ![apo383](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/apo383/32/11272_2.png) [@apo383](https://discourse.julialang.org/u/apo383)\
**Post date:** [August 17, 2021, 1:26am UTC](https://discourse.julialang.org/t/parsimonious-way-to-unpack-big-structs/66522/6 "2021-08-17T01:26:27Z")

</div>

> [@Albert\_Zevelev](#):
>
> it works, but only w/ `@with_kw struct`

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.

```julia
@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](https://2ality.com/2011/06/with-statement.html).

---

<div class="post-metadata">

**Author:** ![amrods](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/amrods/32/2543_2.png) [@amrods](https://discourse.julialang.org/u/amrods)\
**Post date:** [August 17, 2021, 1:45am UTC](https://discourse.julialang.org/t/parsimonious-way-to-unpack-big-structs/66522/7 "2021-08-17T01:45:09Z")

</div>

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?

---

<div class="post-metadata">

**Author:** ![Albert\_Zevelev](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/albert_zevelev/32/11844_2.png) [@Albert\_Zevelev](https://discourse.julialang.org/u/Albert_Zevelev)\
**Post date:** [August 17, 2021, 2:07am UTC](https://discourse.julialang.org/t/parsimonious-way-to-unpack-big-structs/66522/8 "2021-08-17T02:07:39Z")

</div>

@amrods Can you show us what that looks like?

---

<div class="post-metadata">

**Author:** ![amrods](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/amrods/32/2543_2.png) [@amrods](https://discourse.julialang.org/u/amrods)\
**Post date:** [August 17, 2021, 4:38am UTC](https://discourse.julialang.org/t/parsimonious-way-to-unpack-big-structs/66522/9 "2021-08-17T04:38:05Z")

</div>

using your example, it would be

```julia
using ComponentArrays
using UnPack

p = ComponentArray(α=0.1, β=0.2, γ=0.3)

function f2(x; p=p)
    @unpack α, β, γ = p
    return x^(α) + x^(β) + x^(γ)
end

```

I also like to organize the parameters inside the ComponentArray, for example:

```julia
using ComponentArrays
using UnPack

# full model
function model(p; data=data)
    @unpack p1, p2 = p
    @unpack data1, data2 = data
    
    # component 1
    r1 = f1(p1; data=data1)

    # component 2
    r2 = f2(p2; data=data2)

    return sum(r1 .* r2)
end

function f1(p1; data=data)
    @unpack α, β = p1
    @unpack x, y = data
    α .* x + β .* y
end

function f2(p2; data=data)
    @unpack γ, ψ = p2
    @unpack x, y = data
    γ[1] .* x[:, 1] + γ[2] .* x[:, 2] + ψ .* y
end

data = ComponentArray(data1 = (x = rand(100), y = rand(100)),
                      data2 = (x = rand(100, 2), y = rand(100)))
p = ComponentArray(p1 = (α = 1.0, β = 0.5),
                   p2 = (γ = [10.0, 15.0], ψ = 11.0))

model(p; data=data)

```

---

<div class="post-metadata">

**Author:** ![amrods](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/amrods/32/2543_2.png) [@amrods](https://discourse.julialang.org/u/amrods)\
**Post date:** [August 17, 2021, 5:59am UTC](https://discourse.julialang.org/t/parsimonious-way-to-unpack-big-structs/66522/10 "2021-08-17T05:59:12Z")

</div>

I should also say that you can then throw `model` into an optimizer:

```julia
opt = optimize(p -> -model(p; data=data), p)

```

That’s the advantage of `ComponentArrays.jl`.

---

<div class="post-metadata">

**Author:** ![Raf](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/raf/32/3383_2.png) [@Raf](https://discourse.julialang.org/u/Raf)\
**Post date:** [August 17, 2021, 2:50pm UTC](https://discourse.julialang.org/t/parsimonious-way-to-unpack-big-structs/66522/11 "2021-08-17T14:50:09Z")

</div>

Just keep it explicit… This is my favourite version:

```nohighlight
function f2(x;p=p)
    α, β, γ = p.α, p.β, p.γ
    return x^(α) + x^(β) + x^(γ)
end;

```

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.

---

<div class="post-metadata">

**Author:** ![Raf](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/raf/32/3383_2.png) [@Raf](https://discourse.julialang.org/u/Raf)\
**Post date:** [August 17, 2021, 4:55pm UTC](https://discourse.julialang.org/t/parsimonious-way-to-unpack-big-structs/66522/13 "2021-08-17T16:55:59Z")

</div>

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.

---

<div class="post-metadata">

**Author:** ![jlperla](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jlperla/32/34332_2.png) [@jlperla](https://discourse.julialang.org/u/jlperla)\
**Post date:** [August 17, 2021, 5:05pm UTC](https://discourse.julialang.org/t/parsimonious-way-to-unpack-big-structs/66522/14 "2021-08-17T17:05:02Z")

</div>

Edit: not sure why I deleted the last post, but here it was:

> [@Raf](#):
>
> `α, β, γ = p.α, p.β, p.γ`

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.

* * *

> [@Raf](#):
>
> I dont know, I think new people could be confused by `@unpack` ,

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.

---

<div class="post-metadata">

**Author:** ![apo383](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/apo383/32/11272_2.png) [@apo383](https://discourse.julialang.org/u/apo383)\
**Post date:** [August 17, 2021, 5:40pm UTC](https://discourse.julialang.org/t/parsimonious-way-to-unpack-big-structs/66522/15 "2021-08-17T17:40:38Z")

</div>

If other people will look at the code, I’d prefer to at least give a hint with

```julia
(; 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`.

---

<div class="post-metadata">

**Author:** ![Raf](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/raf/32/3383_2.png) [@Raf](https://discourse.julialang.org/u/Raf)\
**Post date:** [August 17, 2021, 6:00pm UTC](https://discourse.julialang.org/t/parsimonious-way-to-unpack-big-structs/66522/16 "2021-08-17T18:00:59Z")

</div>

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.

---

<div class="post-metadata">

**Author:** ![amrods](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/amrods/32/2543_2.png) [@amrods](https://discourse.julialang.org/u/amrods)\
**Post date:** [August 18, 2021, 7:03am UTC](https://discourse.julialang.org/t/parsimonious-way-to-unpack-big-structs/66522/17 "2021-08-18T07:03:00Z")

</div>

> [@jlperla](#):
>
> 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.

That’s precisely what [drove me to ComponentArrays](https://discourse.julialang.org/t/namedarrays-vs-namedtuples-vs-dict-in-objective-function-to-optimization/42659). It is also very tedious to change the code to try different model pieces that take subsets of a large vector of parameters.

> [@apo383](#):
>
> If other people will look at the code, I’d prefer to at least give a hint with
> 
> ```julia
> (; a, b) = p # unpacking fields
> 
> ```

That is what I do, just with `@unpack`.

---

<div class="post-metadata">

**Author:** ![Ahmed\_Salih](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ahmed_salih/32/206579_2.png) [@Ahmed\_Salih](https://discourse.julialang.org/u/Ahmed_Salih)\
**Post date:** [January 27, 2024, 9:07pm UTC](https://discourse.julialang.org/t/parsimonious-way-to-unpack-big-structs/66522/18 "2024-01-27T21:07:19Z")

</div>

If I was to call this function `f2`a lot of times, would this version be more performant than an `@unpack` solution?

---

<div class="post-metadata">

**Author:** ![bertschi](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bertschi/32/33462_2.png) [@bertschi](https://discourse.julialang.org/u/bertschi)\
**Post date:** [January 28, 2024, 11:39am UTC](https://discourse.julialang.org/t/parsimonious-way-to-unpack-big-structs/66522/19 "2024-01-28T11:39:51Z")

</div>

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`:

```julia
function unpack_1(p)
    Parameters.@unpack α, β, γ = p
    α + β + γ
end
function unpack_2(p)
    SimpleUnPack.@unpack α, β, γ = p
    α + β + γ
end
function unpack_3(p)
    α, β, γ = p.α, p.β, p.γ
    α + β + γ
end

```

In any case, you can always check fo yourself:

```julia-repl
julia> @code_typed unpack_1(p)
CodeInfo(
1 ─ %1 = Base.getfield(p, :α)::Any
│ %2 = Base.getfield(p, :β)::Any
│ %3 = Base.getfield(p, :γ)::Any
│ %4 = (%1 + %2 + %3)::Any
└── return %4
) => Any

julia> @macroexpand SimpleUnPack.@unpack α, β, γ = p
:((; α, β, γ) = p)

```

---

<div class="post-metadata">

**Author:** ![Ahmed\_Salih](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ahmed_salih/32/206579_2.png) [@Ahmed\_Salih](https://discourse.julialang.org/u/Ahmed_Salih)\
**Post date:** [January 28, 2024, 11:44am UTC](https://discourse.julialang.org/t/parsimonious-way-to-unpack-big-structs/66522/20 "2024-01-28T11:44:07Z")

</div>

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

Kind regards
