Julia Metaprogramming Guide — Macros, Expressions, and Code Generation
In this tutorial, you will learn about Julia Metaprogramming Guide. We cover key concepts, practical examples, and best practices to help you master this topic.
Julia metaprogramming treats code as data through expressions (Expr objects), with macros (@macroname) transforming AST at parse time, eval for dynamic execution, and @generated functions for compile-time Code Generation based on type information.
Expressions
# Create expression
ex = :(x + y)
# Inspect
dump(ex)
# Expr(:call, :+, :x, :y)
# Build programmatically
ex = Expr(:call, :+, :x, :y)
eval(ex)
# Quote blocks
ex = quote
x = 1
y = 2
x + y
end
eval(ex) # 3
Basic Macros
# Define a macro
macro sayhello(name)
return :(println("Hello, ", $name))
end
# Usage
@sayhello "Alice"
# Expands to: println("Hello, ", "Alice")
# Macro with expressions
macro timeit(expr)
return quote
local start = time()
local result = $expr
local elapsed = time() - start
println("Elapsed: $(elapsed)s")
result
end
end
Built-in Macros
# @time - time execution
@time sleep(1)
# @show - print and return
x = 42
@show x + 1 # prints "x + 1 = 43"
# @assert
@assert 1 + 1 == 2
# @which - show method dispatch
@which 1 + 2
# @elapsed - just the time
t = @elapsed sleep(1)
# @allocated - memory allocation
bytes = @allocated rand(1000)
Generated Functions
# @generated - generate code at compile time
@generated function foo(x)
if x <: Integer
return :(println("Integer: $x"))
elseif x <: AbstractFloat
return :(println("Float: $x"))
else
return :(println("Other"))
end
end
# The compiled code depends on the concrete type
foo(42) # "Integer: 42"
foo(3.14) # "Float: 3.14"
Common Mistakes
1. Variable capture in macros
Macro-expanded code shares the caller's scope. Use esc() to avoid variable capture: :(let x = $(esc(x)) ... end).
2. Overusing eval
eval is slow and breaks compilation. Prefer macros for compile-time work. eval is for dynamic code loading.
3. Macro hygiene
Julia macros are hygienic (generated variables are unique). Use esc() intentionally when you need to access caller scope.
Practice Questions
1. How do you define a macro?
macro name(expr) ... return transformed_expr end. Usage: @name arg.
2. What does the : prefix do?
Quotes an expression without evaluating it. :(1 + 2) creates an Expr without computing.
3. What is the difference between eval and a macro? eval runs at runtime and is slow. Macros run at parse time and are compiled with the surrounding code.
FAQ
{{< faq question="What is hygienic macro?" >}} A macro that doesn't accidentally capture variables from the caller's scope. Julia macros are hygienic by default. {{< /faq >}}
{{< faq question="What does @generated do?" >}} Creates a function whose body depends on the argument types. The specialization is compiled once per concrete type. {{< /faq >}}
{{< faq question="How do I debug macro expansion?" >}}
Use @macroexpand @macroname args to see what the macro expands to.
{{< /faq >}}
What's Next
Now learn about the Julia type system.
| Topic | Description | Link |
|---|---|---|
| Type System | Advanced types | {{< ref "17-type-system" >}} |
| Performance | Performance tips | {{< ref "18-performance" >}} |
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro