# How to write a macro whose generated code depends on a runtime expression?

**URL:** <https://discourse.julialang.org/t/how-to-write-a-macro-whose-generated-code-depends-on-a-runtime-expression/56635>\
**Category:** General Usage\
**Tags:** macros\
**Created:** [March 6, 2021, 11:13pm UTC](https://discourse.julialang.org/t/how-to-write-a-macro-whose-generated-code-depends-on-a-runtime-expression/56635 "2021-03-06T23:13:43Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![marty0801](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/marty0801/32/12715_2.png) [@marty0801](https://discourse.julialang.org/u/marty0801)\
**Post date:** [March 6, 2021, 11:13pm UTC](https://discourse.julialang.org/t/how-to-write-a-macro-whose-generated-code-depends-on-a-runtime-expression/56635/1 "2021-03-06T23:13:43Z")

</div>

I want to define several `struct`s having nearly identical bodies, and avoid repetitive coding using macros. Example: The `A`s have identical bodies; `B` adds a single field.

```julia
struct A1
   a::Int
end 

struct A2
   a::Int 
end 

struct B
   a::Int
   b::Int
end 

```

I’d like to write a single macro that allows me to generate this code. What I’ve tried:

```julia
macro genstruct(name)
   expr= quote
      struct $name 
         a::Int
      end
   end
   return expr
end 

```

allows me to generate A1 and A2, but not B. Can I modify genstruct in such a way as to enable creation of B as well? Is there another approach I should be taking?

---

<div class="post-metadata">

**Author:** ![rdeits](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rdeits/32/286_2.png) [@rdeits](https://discourse.julialang.org/u/rdeits)\
**Post date:** [March 6, 2021, 11:35pm UTC](https://discourse.julialang.org/t/how-to-write-a-macro-whose-generated-code-depends-on-a-runtime-expression/56635/2 "2021-03-06T23:35:21Z")

</div>

Sure, here’s a simple version that generates all of your examples:

```julia
julia> macro genstruct(name, fields...)
         quote
           struct $(esc(name))
             $((:($field::Int) for field in fields)...)
           end
         end
       end
@genstruct (macro with 1 method)

julia> @macroexpand @genstruct A1 a
quote
    #= REPL[10]:3 =#
    struct A1
        #= REPL[10]:4 =#
        a::Main.Int
    end
end

julia> @macroexpand @genstruct A2 a
quote
    #= REPL[10]:3 =#
    struct A2
        #= REPL[10]:4 =#
        a::Main.Int
    end
end

julia> @macroexpand @genstruct B a b
quote
    #= REPL[10]:3 =#
    struct B
        #= REPL[10]:4 =#
        a::Main.Int
        b::Main.Int
    end
end

```

---

<div class="post-metadata">

**Author:** ![marty0801](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/marty0801/32/12715_2.png) [@marty0801](https://discourse.julialang.org/u/marty0801)\
**Post date:** [March 7, 2021, 1:26pm UTC](https://discourse.julialang.org/t/how-to-write-a-macro-whose-generated-code-depends-on-a-runtime-expression/56635/3 "2021-03-07T13:26:01Z")

</div>

Thanks. I’m struggling to understand the “splatting interpolation” you showed; I tried to break it out into a conventional for-loop, which I thought should have been equivalent to your syntax, but it fails:

```julia
macro makestruct(name, fields...) 
    quote 
        struct $(esc(name))
            for field in fields 
                :($(esc(field))::Int)
            end
        end
    end
end
julia> @makestruct A a
ERROR: syntax: field name "Array{Any, (0,)}[]" is not a symbol around /home/elliot/Documents/Programming/JuliaNotesVSCode/abstract_type_algebra.jl:31

```

Line 31 is `for field in fields`.

Evidently for-loop expressions are not allowed in macros? What about other control flow syntax like `if...end`?

---

<div class="post-metadata">

**Author:** ![rdeits](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rdeits/32/286_2.png) [@rdeits](https://discourse.julialang.org/u/rdeits)\
**Post date:** [March 9, 2021, 3:39am UTC](https://discourse.julialang.org/t/how-to-write-a-macro-whose-generated-code-depends-on-a-runtime-expression/56635/4 "2021-03-09T03:39:35Z")

</div>

> [@marty0801](#):
>
> Evidently for-loop expressions are not allowed in macros?

No–the problem is that your macro is producing a `for` loop in the _code_ returned by the macro. You’re producing a struct definition that looks something like:

```julia
struct A
  for field in fields
    x::Int
  end
end

```

That is, you’re mixing up the part of the macro that generates the code (which can have any control flow you want) with the actual code that the macro produces (which can also have any control flow as long as the result is valid Julia code).

This is easier to work with if you start with a simpler example. Given a list of args, let’s produce an array literal `[arg[1], arg[2], ...]` You don’t need a macro for this, but it’s a useful learning exercise.

Here’s a first attempt:

```julia
julia> macro foo(args...)
         quote
           [
             $(esc(args))
           ]
         end
       end
@foo (macro with 1 method)

```

Note that this doesn’t quite work:

```julia
julia> @macroexpand @foo a b c
quote
    #= REPL[14]:3 =#
    [(:a, :b, :c)]
end

```

We’ve created an array, but it only has one element-- the tuple `(:a, :b, :c)`. We need to turn the tuple `args` into multiple separate arguments using `...`:

```julia
julia> macro foo(args...)
         quote
           [
             $(esc.(args)...)
           ]
         end
       end
@foo (macro with 1 method)

```

This gives the desired result:

```julia
julia> @macroexpand @foo a b c
quote
    #= REPL[23]:3 =#
    [a, b, c]
end

```

If this is still confusing (it is for me, since it took me a few tries to get my example right), then you might want to try building the expressions directly, rather than messing around with splatting. For example, we can create an empty array literal with:

```julia
julia> Expr(:vect)
:([])

```

You can figure out the `vect` name by using `dump` to look at an existing expression:

```julia
julia> dump(:([a, b, c]))
Expr
  head: Symbol vect
  args: Array{Any}((3,))
    1: Symbol a
    2: Symbol b
    3: Symbol c

```

So to produce `[a, b, c]`, we need a `:vect` `Expr` with arguments `:a`, `:b`, and `:c`. That’s easy to make:

```julia
julia> Expr(:vect, :a, :b, :c)
:([a, b, c])

```

and we can use it in our macro as well:

```julia
julia> macro foo2(args...)
         Expr(:vect, esc.(args)...)
       end
@foo2 (macro with 2 methods)

julia> @macroexpand @foo2 a b c
:([a, b, c])

```

If you’d rather do it with a loop, you can:

```julia
julia> macro foo3(args...)
         result = Expr(:vect)
         for arg in args
           push!(result.args, esc(arg))
         end
         return result
       end
@foo3 (macro with 1 method)

julia> @macroexpand @foo3 a b c
:([a, b, c])

```

You can do the same thing for your `struct` example:

```julia
julia> dump(:(
         struct A
           a::Int
         end
       ))
Expr
  head: Symbol struct
  args: Array{Any}((3,))
    1: Bool false
    2: Symbol A
    3: Expr
      head: Symbol block
      args: Array{Any}((2,))
        1: LineNumberNode
          line: Int64 3
          file: Symbol REPL[36]
        2: Expr
          head: Symbol ::
          args: Array{Any}((2,))
            1: Symbol a
            2: Symbol Int

```

It’s a bit more complicated, which is why it’s nice to use a shortcut like I wrote in my original post, but you can always create exactly the macro behavior you want by building up the `Expr` objects directly. Using `dump` is a great way to figure out what the final `Expr` tree should look like.

---

<div class="post-metadata">

**Author:** ![marty0801](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/marty0801/32/12715_2.png) [@marty0801](https://discourse.julialang.org/u/marty0801)\
**Post date:** [March 10, 2021, 1:31am UTC](https://discourse.julialang.org/t/how-to-write-a-macro-whose-generated-code-depends-on-a-runtime-expression/56635/5 "2021-03-10T01:31:55Z")

</div>

Hey thanks very much, especially for pointing out that `esc` can be broadcast. (For some reason I tend to forget things I already know about Julia when working in macro-land…) Thanks again.
