# 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]
e
end
@swap_args(2/3)
1.50
``````

I will write up a compendium over the next few days and link it at the top of the WikiBook metaprogramming page: Introducing Julia/Metaprogramming - Wikibooks, open books for an open world.

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

π

6 Likes

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.

3 Likes

Chaining macros? A simple one below:

``````link(a, b) = quote
let _ = \$a
b
end
end

``````
2 Likes

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.

2 Likes

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
end
Expr(:block, :(t = \$(esc(x))), ex)
end
``````
9 Likes

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

(imo)
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.

1 Like

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.

1 Like

I had a problem earlier today of transforming

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

into a statement with `muladd` function calls:

``````muladd(a1,b1,muladd(a2,b2,muladd(a3,b3,a4*b4))))
``````

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

``````
@assert ex.args != [:+]
@assert ex.args[1] == :+
end

if length(ex.args) == 2
return ex.args[2]
else
a, b = ex.args[2].args[2:end]
end
end
``````

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

3 Likes

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

``````using MacroTools
using ChainMap

+(a_*b_) => Expr(:call, :*, a, b)
+(a_*b_, c__) => muladd_together(a, b, c)
end

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

end
``````
1 Like

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)
end

return mul
else
a, b = operands(mul)
end
end
end

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

Here is the dumb thing + error handling and tests: https://gist.github.com/fcard/12a49827cc26a197d4d1e75481216176
All valid ways of doing the same thing, of course.

4 Likes

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.

2 Likes

Well hey, if we’re macro-golfing:

``````macro muladd(ex)
foldr((a, b) -> :(muladd(\$(a.args[2:end]...), \$b)), ex.args[2:end])
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.

4 Likes

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

``````macro muladd(ex)
MacroTools.prewalk(ex) do ex
@capture(ex, +(x_ * y_, cs__)) || return ex
end |> esc
end

a = a1*b1 + a2*b2 + a3*b3
b = a4*b4 + f(a*b + c)
# becomes
``````
7 Likes
``````macro muladd(ex)
end

operands = collect(zip(

last_operation = :(\$(operands[end][1]) * \$(operands[end][2]))

foldr(last_operation, operands[1:end-1]) do xs, r
end
end
``````

?
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

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.

5 Likes

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.

2 Likes

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
a
end
``````
``````julia> t = [:a, :b];

julia> :((\$(t...),))
:((a, b))
``````
1 Like

Oh that’s much better, thanks!

1 Like