Julia represents its own code as a data structure accessible from the language itself. Since code is represented by objects that can be created and manipulated from within the language, it is possible for a program to transform and generate its own code, that is to create powerful macros (the term "metaprogramming" refers to the possibility to write code that write codes that is then evaluated).
Note the difference with C or C++ macros. There, macros work performing textual manipulation and substitution before any actual parsing or interpretation occurs.
In Julia, macros works when the code has been already parsed and organised in a syntax tree, and hence the semantic is much richer and allows for much more powerful manipulations.
# Macros in C++ (example from FFSM++)
#=
#define CONSTRAIN_START_LOOP(pVector,cn) \
for (uint r1=0;r1<l2r.size();r1++){ \
for (uint r2=0;r2<l2r[r1].size();r2++){ \
for (uint p=0;p<(pVector).size();p++){ \
int psec = p+nPriPr; \
cix = gix((cn), r1, r2, p);
#define CONSTRAIN_END_LOOP \
}}}
# using the macro to avoid writing the multiple loop every time..
// mkteq2(i,p_tr).. RVAR('dl',i,p_tr)+sum(j,EXP(i,j,p_tr)) =e= RVAR('sl',i,p_tr)+ sum(b,EXP(b,i,p_tr)); // h1
CONSTRAIN_START_LOOP(secPr, 0) // attenction! you have to give the same order number as you inserted in the cons vector
g[cix] = x[gix("dl",r1,r2,psec)]-x[gix("sl",r1,r2,psec)];
for (uint r2To=0;r2To<l2r[r1].size();r2To++){
g[cix] += x[gix("rt",r1,r2,psec,r2To)]-x[gix("rt",r1,r2To,psec,r2)];
}
CONSTRAIN_END_LOOP
=#
You can create and save unevaluated expressions using either:
myexpr1 = :(1+2*3) # save the `1+2*3` expression in the `myexpr1` expression
myexpr2 = quote 1+2*3 end
myexpr3 = Meta.parse("1+2*3")
myexpr4 = Expr(:call, :+, 1, Expr(:call, :*, 2, 3))
:(1 + 2 * 3)
# Such expressions can then be evaluated in a second moment:
eval(myexpr1) # here the expression is evaluated and the code returns 7
7
dump(myexpr4)
Expr head: Symbol call args: Array{Any}((3,)) 1: Symbol + 2: Int64 1 3: Expr head: Symbol call args: Array{Any}((3,)) 1: Symbol * 2: Int64 2 3: Int64 3
# Macros in Julia
# Macros in Julia take one or more input expressions and return a modified expressions (at parse time).
# The modified expression is then evaluated at run-time.
# This contrast with normal functions that, at runtime, take the input values (arguments) and return a computed value.
# Macro definition..
macro unless(test_expr, branch_expr)
quote
if !$test_expr
$branch_expr
end
end
end
@unless (macro with 1 method)
# Macro call..
array = [1, 2, 'b']
@unless 3 in array println("array does not contain 3") # here test_expr is "3 in array" and branch_expr is "println("array does not contain 3")"
array does not contain 3
# The "expanded" macro will look like:
array = [1, 2, 'b']
if !(3 in array)
println("array does not contain 3")
end
array does not contain 3
# We can reuse the macro for different expressions..
@unless length(array) >= 10 println("array has fewer than 10 elements")
array has fewer than 10 elements