Julia Metaprogramming
Metaprogramming is writing code that manipulates code itself. Julia's metaprogramming capabilities are very powerful, supporting expression manipulation, macros, and code generation.
Expressions and Symbols
Symbols
julia
# Create symbol
s = :hello
println(s) # hello
println(typeof(s)) # Symbol
# Symbol and string conversion
s = Symbol("world")
str = String(:hello)
# Symbol comparison
println(:apple == :apple) # true
println(:apple == :banana) # falseExpressions (Expr)
julia
# Use :() to quote expression
expr = :(1 + 2)
println(expr) # :(1 + 2)
println(typeof(expr)) # Expr
# View expression structure
println(expr.head) # :call
println(expr.args) # Any[:+, 1, 2]
# Use Meta.parse to parse string
expr = Meta.parse("x + y * z")
println(expr)
# Using quote block
expr = quote
x = 1
y = 2
x + y
end
println(expr)Expression Structure
julia
expr = :(a + b * c)
# Use dump to view full structure
dump(expr)
# Expr
# head: Symbol call
# args: Array{Any}((3,))
# 1: Symbol +
# 2: Symbol a
# 3: Expr
# head: Symbol call
# args: Array{Any}((3,))
# 1: Symbol *
# 2: Symbol b
# 3: Symbol cExpression Manipulation
Building Expressions
julia
# Manually build expression
expr = Expr(:call, :+, 1, 2)
println(expr) # :(1 + 2)
println(eval(expr)) # 3
# Build assignment expression
expr = Expr(:(=), :x, 10)
println(expr) # :(x = 10)
# Build function call
expr = Expr(:call, :println, "Hello, World!")
eval(expr) # Hello, World!Modifying Expressions
julia
expr = :(x + y)
# Modify operator
expr.args[1] = :*
println(expr) # :(x * y)
# Modify operands
expr.args[2] = :a
expr.args[3] = :b
println(expr) # :(a * b)Expression Interpolation
julia
# Use $ for interpolation
x = 10
expr = :($x + 20)
println(expr) # :(10 + 20)
# Interpolate symbol
var = :myvar
expr = :($var = 100)
println(expr) # :(myvar = 100)
# Interpolate expression
sub_expr = :(a + b)
expr = :($sub_expr * c)
println(expr) # :((a + b) * c)eval and Execution
julia
# eval executes expression in global scope
expr = :(x = 10)
eval(expr)
println(x) # 10
# Evaluate expression
result = eval(:(2^10))
println(result) # 1024
# Execute code block
eval(quote
a = 5
b = 10
println(a + b)
end)
# Output: 15
# Dynamically generate code
for op in (:+, :-, :*, :/)
@eval function calc(a, b, ::Val{$(QuoteNode(op))})
return $op(a, b)
end
endMacros
Macro Basics
julia
# Define macro
macro sayhello(name)
return :(println("Hello, ", $name, "!"))
end
# Call macro
@sayhello "Julia" # Hello, Julia!
# Macro receives expression, returns expression
macro show_expr(expr)
println("Expression: ", expr)
return expr
end
@show_expr 1 + 2 # Prints expression, returns 3Macro Expansion
julia
macro debug(expr)
return quote
println("Expression: ", $(string(expr)))
println("Result: ", $expr)
end
end
@debug 1 + 2
# Expression: 1 + 2
# Result: 3
# View macro expansion
println(@macroexpand @debug 1 + 2)Hygienic Macros
Julia's macros are "hygienic", automatically handling variable name conflicts:
julia
macro make_var()
return quote
x = 100 # This x is macro-internal
end
end
x = 10
@make_var
println(x) # 10 (external x unaffected)
# Use esc to break hygiene
macro make_global_var()
return :($(esc(:x)) = 100)
end
x = 10
@make_global_var
println(x) # 100 (external x modified)Common Macro Patterns
julia
# Timing macro
macro timed(expr)
return quote
local start = time()
local result = $(esc(expr))
local elapsed = time() - start
println("Elapsed: $(elapsed) seconds")
result
end
end
@timed begin
sleep(1)
42
end
# Conditional compilation
macro ifelse_example(condition, true_expr, false_expr)
return quote
if $(esc(condition))
$(esc(true_expr))
else
$(esc(false_expr))
end
end
end
# Logging macro
macro log(level, message)
return quote
println("[$(uppercase(string($level)))] $($message)")
end
end
@log :info "Application started"Generated Functions
@generated Functions
julia
@generated function mysum(x::NTuple{N, T}) where {N, T}
# Generate code at compile time
expr = :(x[1])
for i in 2:N
expr = :($expr + x[$i])
end
return expr
end
println(mysum((1, 2, 3))) # 6
println(mysum((1, 2, 3, 4))) # 10Type-Specialized Code Generation
julia
@generated function type_info(::Type{T}) where T
# Generate different code based on type
if T <: Integer
return :(println("This is an integer type"))
elseif T <: AbstractFloat
return :(println("This is a float type"))
else
return :(println("This is another type"))
end
end
type_info(Int64) # This is an integer type
type_info(Float64) # This is a float type
type_info(String) # This is another typeCommon Built-in Macros
@show
julia
x = 10
y = 20
@show x # x = 10
@show x + y # x + y = 30
@show x, y # (x, y) = (10, 20)@time and @elapsed
julia
@time begin
sum(rand(1000000))
end
# Outputs time and memory allocation info
elapsed = @elapsed sum(rand(1000000))
println("Elapsed: $(elapsed) seconds")@assert
julia
x = 10
@assert x > 0 "x must be positive"
# @assert x < 0 "x must be negative" # Would error@inline and @noinline
julia
@inline function fast_add(a, b)
return a + b
end
@noinline function no_inline_add(a, b)
return a + b
end@simd and @threads
julia
# SIMD vectorization
function sum_simd(arr)
s = zero(eltype(arr))
@simd for x in arr
s += x
end
return s
end
# Multithreading
using Base.Threads
function parallel_sum(arr)
n = length(arr)
partial = zeros(eltype(arr), nthreads())
@threads for i in 1:n
partial[threadid()] += arr[i]
end
return sum(partial)
endPractical Examples
Auto-generate Getter/Setter
julia
macro auto_accessors(struct_name, fields...)
getters = []
setters = []
for field in fields
getter = Symbol("get_", field)
setter = Symbol("set_", field)
push!(getters, quote
$(esc(getter))(obj::$(esc(struct_name))) = obj.$field
end)
push!(setters, quote
function $(esc(setter))(obj::$(esc(struct_name)), val)
obj.$field = val
end
end)
end
return quote
$(getters...)
$(setters...)
end
end
mutable struct Person
name::String
age::Int
end
@auto_accessors Person name age
p = Person("Alice", 30)
println(get_name(p)) # Alice
set_age(p, 31)
println(get_age(p)) # 31Domain Specific Language (DSL)
julia
# Simple test DSL
macro test_suite(name, tests)
return quote
println("Running test suite: ", $name)
$(esc(tests))
println("Tests complete")
end
end
macro it(description, body)
return quote
print(" Test: ", $description, " ... ")
try
$(esc(body))
println("✓")
catch e
println("✗")
println(" Error: ", e)
end
end
end
@test_suite "Math operations" begin
@it "addition works" begin
@assert 1 + 1 == 2
end
@it "multiplication works" begin
@assert 2 * 3 == 6
end
endSQL Query Builder
julia
macro select(fields, table)
field_str = join([string(f) for f in fields.args], ", ")
return :(println("SELECT $($field_str) FROM $($table)"))
end
@select((id, name, email), users)
# Output: SELECT id, name, email FROM usersDebugging Tips
julia
# View macro expansion
@macroexpand @show x
# View generated code
@code_lowered sum([1,2,3])
@code_typed sum([1,2,3])
@code_llvm sum([1,2,3])
@code_native sum([1,2,3])
# Print expression tree
Meta.show_sexpr(:(x + y * z))Best Practices
- Use macros sparingly: Only use macros when necessary, prefer functions
- Keep macros simple: Put complex logic in helper functions
- Use esc correctly: Escape expressions that need evaluation in caller's scope
- Test macro expansion: Use
@macroexpandto verify generated code - Document macros: Provide clear documentation for macros
Next Steps
After learning metaprogramming, you've mastered Julia's core features!
Continue exploring:
- Julia's parallel computing
- Package development
- Performance optimization techniques
- Interoperability with other languages
Congratulations on completing the Julia tutorial!