Metaprogramming: Code as Data
Prerequisites: Structs
Julia code is represented as ordinary Julia data structures, so programs can generate and transform code. This is called metaprogramming.
Symbols and expressions
s = :total
println(typeof(s))
ex = :(a + b * 2)
println(typeof(ex))
println(ex.head, " ", ex.args)
a, b = 1, 3
println(eval(ex))
parsed = Meta.parse("sqrt(16) + 1")
println(parsed, " = ", eval(parsed))
Symbol
Expr
call Any[:+, :a, :(b * 2)]
7
sqrt(16) + 1 = 5.0
- A symbol (
:total) is an interned name. :(...)quotes code: it becomes anExprinstead of running.- An
Exprhas ahead(the kind of expression — here a functioncall) andargs. evalruns an expression in the global scope of the current module;Meta.parseturns a string into an expression.
Inspecting structure
ex = :(x > 0 ? "pos" : "neg")
dump(ex)
Expr
head: Symbol if
args: Array{Any}((3,))
1: Expr
head: Symbol call
args: Array{Any}((3,))
1: Symbol >
2: Symbol x
3: Int64 0
2: String "pos"
3: String "neg"
dump shows the full tree. Meta.show_sexpr(ex) prints it in Lisp
style.
Building expressions with interpolation
op = :*
ex = :($op(6, 7))
println(ex, " => ", eval(ex))
terms = [:(x^$i) for i in 1:3]
poly = Expr(:call, :+, terms...)
println(poly)
x = 2
println(eval(poly))
6 * 7 => 42
x ^ 1 + x ^ 2 + x ^ 3
14
$ inside a quote splices a value in. Expr(:call, f, args...) builds a
call directly.
Generating code with eval
@eval (a quote plus eval) is often used to define many similar
methods in a loop:
for (name, factor) in [(:km_to_m, 1000), (:m_to_cm, 100), (:kg_to_g, 1000)]
@eval $name(x) = x * $factor
end
println(km_to_m(3), " ", m_to_cm(2.5), " ", kg_to_g(0.2))
3000 250.0 200.0
Julia's own Base uses this to define operators for many types. Don't
use eval for ordinary logic, though — it runs in global scope, is slow,
and is hard to read. If a function would do, write a function.