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:
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> macro foo(args...)
quote
[
$(esc(args))
]
end
end
@foo (macro with 1 method)
Note that this doesn’t quite work:
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> macro foo(args...)
quote
[
$(esc.(args)...)
]
end
end
@foo (macro with 1 method)
This gives the desired result:
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> Expr(:vect)
:([])
You can figure out the vect
name by using dump
to look at an existing expression:
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> Expr(:vect, :a, :b, :c)
:([a, b, c])
and we can use it in our macro as well:
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> 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> 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.