Skip to content
AgileLogics
6 min read

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 an Expr instead of running.
  • An Expr has a head (the kind of expression — here a function call) and args.
  • eval runs an expression in the global scope of the current module; Meta.parse turns 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.