Looping within a quote block in a macro

Hi,

I have a bit of a beginner question regarding macros. I’m well aware that macros are tricky and in many cases can just be replaced by functions, but I think for one application I have they can significantly reduce boiler-plate and allow for significant user flexibility. But I’m struggling a bit to do things I think should be straightforward. A bit of a toy example I have is below:

macro example_macro(name, values)
    quote
        struct $name
            $(values.args[1])
            $(values.args[2])
        end
    end
end
@macroexpand @example_macro(MyStruct, [value1, value2])

This is meant to define a struct with a given name and with a set of fields that are defined by the user. Utility in here is that there is a lot of boiler plate code and functions that would be defined for this composite type and that only require knowing these names.

Issue with the above example is that I am explicitly extracting the two elements of the given array, while I would like to programatically loop over however many there are. This seems like a simple operation but I’m finding it tricky to find info on how to perform these kinds of operations from online resources (sorry if I missed something obvious). How one would go about doing this? I know I can manually build up the full expression I want, but I like how the quote block provides nice readability for the resulting code.

And again, I know meta-programming might not be the solution but just want to understand it better and learn how to use it to see if it can effectively serve me in some cases.

For your example, this works, too:

struct $name
    $(values.args...)
end

In case of the toy example, what @jules suggested works. For a slightly more complicated scenario, where you want to do some processing on the individual arguments, it doesn’t. In this case, you have to dig a bit deeper, e.g.:

julia> macro example_macro(name, values...)
    fields = map(values) do value
        :($(value)::Any = nothing)
    end
    return quote
        @kwdef struct $(name)
            $(fields...)
        end
    end
end
@example_macro (macro with 1 method)

julia> @macroexpand @example_macro foo bar baz
quote
    #= REPL[3]:6 =#
    begin
        #= util.jl:612 =#
        begin
            $(Expr(:meta, :doc))
            struct foo
                #= REPL[3]:7 =#
                bar::Main.Any
                baz::Main.Any
            end
        end
        #= util.jl:613 =#
        function Main.foo(; bar = Main.nothing, baz = Main.nothing)
            #= REPL[3]:6 =#
            Main.foo(bar, baz)
        end
    end
end

Note that this is still a contrived example, but the key takeaway is that you need to construct the resulting expression bit by bit. Sometimes its easier to construct Expr values directly instead of fiddling with quote expressions and blocks. E.g., instead of :($(value)::Any = nothing) above, I could’ve written

    Expr(:(=), Expr(:(::), value, :Any), :nothing)

as well.

2 Likes

Thanks, this is exactly what I was looking for!

1 Like