Simple metaprogramming exercises/challenges


Could we brainstorm a handful of metaprogramming examples/exercises/challenges?

  • practical or absurd, both welcome
  • with or without solutions, both welcome
  • support explanations welcome

The idea is ‘Learning through Doing’.

Here is an example (Thanks @fcard):

# Write a 'swap_args' macro that reverses operands for simple
#  `<a> <op> <b>` expressions, so ` @swap_args(2/3)` gives 1.5 i.e. 3/2
macro swap_args(e)
    e.args[2:3] = e.args[3:-1:2]   

I will write up a compendium over the next few days and link it at the top of the WikiBook metaprogramming page:

(EDIT:) Looks like we’re getting a good mix of difficulty levels!


Interpolating function arguments

Maybe call that one swap_operands (because swap(a,b) is likely b,a without the operator) .


Another common macro that I often describe to newcomers is @time.

More sophisticated examples of expression transformations may include functions / macros to match expression against pattern or substitute part of expression, e.g.:

expr = :(num ^ 2)
pattern = :(x ^ n)
matchex(expr, pattern)   # ==> Dict(:x => :num, :n => 2)

expr = :(x ^ n)
subs(expr, n=2)   # ==> :(x ^ 2) 

I used these functions extensively in Espresso.jl, but I find them a funny exercise metaprogramming as well.


Chaining macros? A simple one below:

link(a, b) = quote
    let _ = $a

macro chain(as...) = reduce(link, as)


Honestly, I don’t understand any examples here!

edit - By that, I mean I just can’t use these examples into Julia REPL and i feel that this thread would be super useful for beginner’s like me.


One possible example is to generate an inlined polynomial evaluation by Horner’s method, given a variable x and a list of coefficients. This is actually implemented in Base (base/math.jl), but makes a good exercise because it is simple and genuinely practical:

# evaluate p[1] + x * (p[2] + x * (....)), i.e. a polynomial via Horner's rule
macro horner(x, p...)
    ex = esc(p[end])
    for i = length(p)-1:-1:1
        ex = :(muladd(t, $ex, $(esc(p[i]))))
    Expr(:block, :(t = $(esc(x))), ex)


The way I understand @dfdx example, it is a task: implement the matchex and subs macros?


Macro contributors, Balinus and many others need an accompanying example that is easily absorbed and some words about what is happening. A second more recondite example is welcome, too.


Yes, exactly. But you may also find ready-to-use solutions here. The point of these functions as an exercise is to learn how to efficiently traverse and transform Julia expressions, which opens the doors to all kinds of fun stuff like automatic differentiation, devectorization or fast math transformation.


I had a problem earlier today of transforming

@muladd a1*b1 + a2*b2 + a3*b3 + a4*b4

into a statement with muladd function calls:


credit for the neat solution goes to @fcard on Gitter:

macro muladd(ex)
  @assert ex.head == :call
  @assert ex.args != [:+]
  @assert ex.args[1] == :+

function _muladd_meta(ex)
  if length(ex.args) == 2
    return ex.args[2]
    a, b = ex.args[2].args[2:end]
    rest = _muladd_meta(Expr(ex.head, :+, ex.args[3:end]...))
    return :($(Base.muladd)($a, $b, $rest))

This is a good little example of a syntactic sugar macro which can be quite useful for performance.


Here’s another implementation of muladd using packages to make things pretty?

using MacroTools
using ChainMap

muladd_(e) = @match e begin
    +(a_*b_) => Expr(:call, :*, a, b)
    +(a_*b_, c__) => muladd_together(a, b, c)

muladd_together(a, b, c) = @chain begin
    Expr(:call, :+, c...)
    Expr(:call, Base.muladd, a, b, _)

macro muladd(e)


Once again one of my macros was stricken with MacroTools… A bit of my soul dies each time. (jkjkjk!)
Personally I prefer to use julia’s own abstractions to make things “pretty”:

macro muladd(add)

function to_muladd(add)
  let mul = operands(add)[1]
    if length(operands(add)) == 1
      return mul
      a, b = operands(mul)
      rest = to_muladd(:( +($(operands(add)[2:end]...)) ))
      return :($(Base.muladd)($a, $b, $rest))

operator(ex) = ex.args[1]
operands(ex) = ex.args[2:end]

Here is the dumb thing + error handling and tests:
All valid ways of doing the same thing, of course.


For a simple, but fun exercise, try writing a macro that turns expressions into anonymous functions, using _ as arguments. An example of the macro being used would be:

map(@par(_ * max(_, 2)), 1:4, 4:-1:1)

# 4 6 6 8

Another thing you could try is adding an option of labeling the _ arguments with numbers, making _1 the first argument, _2 the second and so on. You could also add type annotations.


Well hey, if we’re macro-golfing:

macro muladd(ex)
  foldr((a, b) -> :(muladd($(a.args[2:end]...), $b)), ex.args[2:end])

As another starting out idea, here’s the simplest thing I can think of that isn’t equivalent to a closure:

(a, b) = (1, 2)
@swap a b
(a, b) == (2, 1)

Should be pretty easy for a beginner to write.


Here’s a harder variation on the muladd challenge: Make it general enough that it can look at any code and transform the inner muladds, keeping everything else the same. Here’s a version using MacroTools; consider me very impressed if anyone can make it cleaner without :slight_smile:

macro muladd(ex)
  MacroTools.prewalk(ex) do ex
    @capture(ex, +(x_ * y_, cs__)) || return ex
    :(Base.muladd($x, $y, $(foldl((a,b)->:($b+$a), cs))))
  end |> esc

a = a1*b1 + a2*b2 + a3*b3
b = a4*b4 + f(a*b + c)
# becomes
a = muladd(a1,b1,muladd(a3,b3,a2 * b2))
b = muladd(a4,b4,f(muladd(a,b,c)))

macro muladd(ex)

function to_muladd(ex)
  is_add_operation(ex) || return ex
  operands = collect(zip(
  last_operation = :($(operands[end][1]) * $(operands[end][2]))
  foldr(last_operation, operands[1:end-1]) do xs, r
    :($(Base.muladd)($(xs[1]), $(xs[2]), $r))
is_add_operation(ex::Expr) = ex.head == :call && !isempty(ex.args) && ex.args[1] == :+
is_add_operation(ex) = false

Of couse I could make this more compact, but I don’t think code golf was ever the point of this. It’s not about the golf, Mike, it’s about the love! (I don’t know what that means)
Nice one with the foldl tho, I keep missing the opportunities to use that :stuck_out_tongue:

Edit: I added this new functionality to the gist of my no-fun version , which I still like despite the hardships we encountered together.
Edit2: It has dawned on me that we may have gone off topic a bit, I will see if I can come up with a few simple macros to make up for it.

Mike Innes really is the He-Man to my Skeletor, I keep trying to take over EterniaJulia by introducing my evil verbosity and taking away all the fun, but he keeps foiling my dastardly plans through the power of GrayskullCodeGolfing.


This is really a great example, because of the cool ways it uses interpolations and escaping. It could be really useful for macro learners, I think, with a line-by-line description of the construction and considerations.


Transform a vector of symbols into a symbol of tuples:
tuplify([:a,:b]) == :((a,b))

My solution works, but it feels a bit kludgy:

function tuplify(x)
    a = Expr(:tuple, x)
    a.args = x

julia> t = [:a, :b];

julia> :(($(t...),))
:((a, b))


Oh that’s much better, thanks!