How to "broadcast" macro interpolation

In Julia, building a simple expression is as easy as inserting a few $s. For example if I write a macro that zeros a variable by setting it to literal 0:

macro zeroit(x::Symbol)
    :($(esc(x)) = 0)
end

But if I want a zerothem macro that zeros multiple variables, I have to build an Expr manually:

macro zerothem(xs...)
    e = Expr(:block)
    e.args = map(collect(xs)) do x
        :($(esc(x)) = 0)
    end
    e
end

Is there a better way to do this?

1 Like

This is good. It’s basically what @unpack does, see here.

Although my advice for writing macros is to always do it in a helper function. i.e.

macro mymacro(args...)
    esc(mymacro_helper(args...))
end

function mymacro_helper(args...)
    ...
end

Here’s how I would do it

julia> macro makezero(args...)
           esc(makezero_helper(args...))
       end;

julia> function makezero_helper(args...)
           assignments = map(args) do arg
               :($arg = 0)
           end
           Expr(:block, assignments...)
       end;
1 Like

With just interpolations (although perhaps too many parentheses):

macro zerothem(xs...)
    quote
        $((:($(esc(x)) = 0;) for x in xs)...)
        nothing # so that it does not print zero
    end
end

Here’s another option:

julia> macro zerothem(xs...)
           :(($(xs...),) = $Iterators.repeated(0); ($(xs...),)) |> esc
       end

so that

julia> @macroexpand @zerothem x y z
quote
    (x, y, z) = (Base.Iterators).repeated(0)
    #= REPL[12]:2 =#
    (x, y, z)
end

and

julia> @btime @zerothem x y z
  0.019 ns (0 allocations: 0 bytes)
(0, 0, 0)

At this point though, one wonders if you even want a macro for this, when you could just write

julia> using Base.Iterators: repeated

julia> a, b, c = repeated(0);

julia> a, b, c
(0, 0, 0)
2 Likes

Thank you for your replies! I am starting to understand it.
So $(exprs...) is a broadcasted macro interpolation, it will insert all elements from exprs into the parent expression. The key is that the “parent expression” needs to be made explicit (blocks, tuples, vectors, etc) so that the parser expects multiple sub-expressions:

  • In case of multiple independent expressions, we need quote $(exprs...) end or :($(exprs...);)
  • In case of tuples, we need :($(exprs...),)

Vectors could be a more interesting example:

elems = (1,2,3)
:[$(elems...);] # => :([1; 2; 3])
:[$(elems...),] # => :([1, 2, 3]), "," is optional

So the trailing ; determines what kind of expressions $(elems...) will form.

1 Like