Macros
Prerequisites: Metaprogramming: code as data
A macro is a function that runs when code is parsed: it
receives expressions and returns a new expression that replaces the
macro call. You've used many: @show, @time, @printf, @.,
@views, @test.
macro twice(ex)
return quote
$(esc(ex))
$(esc(ex))
end
end
@twice println("hello")
macro show_value(ex)
text = string(ex)
return :(println($text, " --> ", $(esc(ex))))
end
x = 5
@show_value x^2 + 1
hello
hello
x ^ 2 + 1 --> 26
macro name(args) ... enddefines@name.- The arguments are expressions, not values:
@show_valuegets:(x^2 + 1)and can turn it into the string"x ^ 2 + 1"as well as evaluate it. esc(ex)means "evaluate this in the caller's scope".
@macroexpand
See what a macro generates:
macro unless(cond, body)
return :(if !$(esc(cond)); $(esc(body)); end)
end
ex = @macroexpand @unless 1 > 2 println("1 is not greater than 2")
println(Base.remove_linenums!(ex))
@unless 1 > 2 println("1 is not greater than 2")
if !(1 > 2)
println("1 is not greater than 2")
end
1 is not greater than 2
Hygiene
Variables created inside a macro get unique names, so they can't clash with the caller's variables:
macro timed_twice(ex)
quote
t = time_ns()
$(esc(ex))
$(esc(ex))
elapsed = time_ns() - t
elapsed >= 0
end
end
t = "my variable"
ok = @timed_twice print("hi ")
println(t, " ", ok)
hi hi my variable true
The macro's t was renamed behind the scenes, so the caller's t was
untouched. Only esc-aped code refers to the caller's names.
A practical macro: @retry
macro retry(n, ex)
quote
local result
for attempt in 1:$(esc(n))
try
result = $(esc(ex))
break
catch err
attempt == $(esc(n)) && rethrow()
println("attempt $attempt failed: ", sprint(showerror, err))
end
end
result
end
end
calls = Ref(0)
function flaky()
calls[] += 1
calls[] < 3 && error("service unavailable")
return "ok after $(calls[]) calls"
end
println(@retry 5 flaky())
attempt 1 failed: service unavailable
attempt 2 failed: service unavailable
ok after 3 calls
A function couldn't do this: it would receive the result of
flaky(), not the code to run again.
When to write a macro
Write a macro only when a function can't do the job — when you need to
see the code itself (its text or structure), control whether or how
often it runs, or create a small domain-specific language (like
JuMP's @constraint or DataFrames' @df). Macros are harder to read
and debug than functions.