# Macro for implicitly defined NamedTuples

**URL:** <https://discourse.julialang.org/t/macro-for-implicitly-defined-namedtuples/114685>\
**Category:** General Usage\
**Tags:** metaprogramming\
**Created:** [May 24, 2024, 3:21pm UTC](https://discourse.julialang.org/t/macro-for-implicitly-defined-namedtuples/114685 "2024-05-24T15:21:01Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![pablosanjose](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/pablosanjose/32/7006_2.png) [@pablosanjose](https://discourse.julialang.org/u/pablosanjose)\
**Post date:** [May 24, 2024, 3:21pm UTC](https://discourse.julialang.org/t/macro-for-implicitly-defined-namedtuples/114685/1 "2024-05-24T15:21:01Z")

</div>

I want to write a macro `imp` that does this

```julia
julia> @imp(; a = 2, b = a^2, c = 3) 
(; a = 2, b = 4, c = 3)

```

Not being any good with metaprogramming I turn to the Julia community:

- Is there any package that already achieves this?
- If not, what’s the best way to go about writint this?

Note that this is very similar to the way @kwdef already works, but with NamedTuples instead of structs. Note also that `f(; a = 2, b = a^2, c = 3) = (a, b, c)` already works, but it is a function not a NamedTuple.

---

<div class="post-metadata">

**Author:** ![pdeffebach](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/pdeffebach/32/10320_2.png) [@pdeffebach](https://discourse.julialang.org/u/pdeffebach)\
**Post date:** [May 24, 2024, 3:31pm UTC](https://discourse.julialang.org/t/macro-for-implicitly-defined-namedtuples/114685/2 "2024-05-24T15:31:40Z")

</div>

I have a package AddToField.jl which does this, kind of. It’s more verbose and has a slightly different use-case, for if you want to do _a lot_ of calculations and only keep a few variables. You have the opposite scenario.

But it’s implementation might help you write your own macro.

```julia
julia> @addnt begin
           @add a = 2
           @add b = a^2
           @add d = 4
       end
(a = 2, b = 4, d = 4)

```

---

<div class="post-metadata">

**Author:** ![abraemer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/abraemer/32/51403_2.png) [@abraemer](https://discourse.julialang.org/u/abraemer)\
**Post date:** [May 24, 2024, 4:27pm UTC](https://discourse.julialang.org/t/macro-for-implicitly-defined-namedtuples/114685/3 "2024-05-24T16:27:49Z")

</div>

I quickly build a small macro that does this (probably not in a very robust way) and wanted to give a small outline on how to approach such a problem 🙂 For the solution look at the end.

When writing a macro a good first step is to write down what the transformation of the source code should actually be. In this case, as you noted, the desired functionality is already present for functions but also for `let` blocks. So it is sufficient for the macro to transform:  
`@imp(; a = 2, b = a^2, c = 3) ` into

```julia
let a = 2, b = a^2, c = 3
    (; a = 2, b = a^2, c = 3) 
end

```

So the next step is looking at the AST to figure out how to puzzle together the desired structure of `Expr`s from the ones we recieve. For that we can use `dump` (and `Base.remove_linenums!` to strip away unnecessary nodes our macro won’t see but that `dump` might pick up).

```julia-repl
julia> dump(Base.remove_linenums!(quote
               let a = 2, b = a^2, c = 3
                       (; a = 2, b = a^2, c = 3) 
               end
       end); maxdepth=12)
Expr
  head: Symbol block
  args: Array{Any}((1,))
    1: Expr
      head: Symbol let
      args: Array{Any}((2,))
        1: Expr
....

```

To get the input of the macro, I recommend actually using a macro, e.g:

```julia
macro dump(expr)
	return :(dump($(QuoteNode(expr))))
end

```

Then you get

```julia
julia> @dump(; a = 2, b = a^2, c = 3) 
Expr
  head: Symbol parameters
  args: Array{Any}((3,))
    1: Expr
      head: Symbol kw
      args: Array{Any}((2,))
        1: Symbol a
        2: Int64 2
....

```

With that it is not very hard to come up with the transformation. Here is your solution:

```julia
macro imp(expr)
	expr.head == :parameters || error("I don't understand this: $(dump(expr;maxdepth=4))")
	bindings = Expr(:block, (Expr(:(=), e.args[1], e.args[2]) for e in expr.args)...)
	body = Expr(:tuple, expr)
	return esc(Expr(:let, bindings, body))
end

```

---

<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:** [May 24, 2024, 5:03pm UTC](https://discourse.julialang.org/t/macro-for-implicitly-defined-namedtuples/114685/4 "2024-05-24T17:03:48Z")

</div>

Nice explanation, your macro suffers from the multiple evaluation problem though:

```julia-repl
julia> @imp(; a = 2, b = @show(a^2), c = 3)
a ^ 2 = 4
a ^ 2 = 4
(a = 2, b = 4, c = 3)

```

Better to reuse the `let` bindings in the expansion:

```julia
macro imp2(expr)
    expr.head == :parameters || error("I don't understand this: $(dump(expr;maxdepth=4))")
    bindings = Expr(:block, (Expr(:(=), e.args[1], e.args[2]) for e in expr.args)...)
    body = Expr(:tuple, (Expr(:(=), e.args[1], e.args[1]) for e in expr.args)...)
    return esc(Expr(:let, bindings, body))
end

```

```julia-repl
julia> @imp2(; a = 2, b = @show(a^2), c = 3)
a ^ 2 = 4
(a = 2, b = 4, c = 3)

```

There is also a corner case due to the parsing of macros:

```julia
@imp(; a = 2, b = a^2, c = 3) # Works
@imp (; a = 2, b = a^2, c = 3) # Does not work ... note the space!

```

Again `@dump` (already defined in `Meta`) is your friend here:

```julia-repl
julia> Meta.@dump(; a = 2, b = a^2, c = 3)
Expr
  head: Symbol parameters
  args: Array{Any}((3,))
    1: Expr
      head: Symbol kw
      ...

# Now, with a space
julia> Meta.@dump (; a = 2, b = a^2, c = 3)
Expr
  head: Symbol tuple
  args: Array{Any}((1,))
    1: Expr
      head: Symbol parameters
      args: Array{Any}((3,))
        1: Expr
          head: Symbol kw
          ...

```

---

<div class="post-metadata">

**Author:** ![abraemer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/abraemer/32/51403_2.png) [@abraemer](https://discourse.julialang.org/u/abraemer)\
**Post date:** [May 24, 2024, 5:10pm UTC](https://discourse.julialang.org/t/macro-for-implicitly-defined-namedtuples/114685/5 "2024-05-24T17:10:32Z")

</div>

Thanks for the comments 🙂 And good catch with the multiple evaluations!

> [@bertschi](#):
>
> Again `@dump` (already defined in `Meta`) is your friend here:

I knew I saw that somewhere but somehow forgot completely that `Meta` exists 😅

If need be, we can easily support both syntax versions with a simple check:

```julia
macro imp2(expr2)
    expr = expr2
    if expr.head == :tuple
        expr = expr.args[1]
    end
    expr.head == :parameters || error("I don't understand this: $(dump(expr;maxdepth=4))")
    bindings = Expr(:block, (Expr(:(=), e.args[1], e.args[2]) for e in expr.args)...)
    body = Expr(:tuple, (Expr(:(=), e.args[1], e.args[1]) for e in expr.args)...)
    return esc(Expr(:let, bindings, body))
end

```
