# 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
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.:
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]))))
end
Expr(:block, :(t = $(esc(x))), ex)
end
(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.
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.
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)
end
muladd_together(a, b, c) = @chain begin
Expr(:call, :+, c...)
muladd_
Expr(:call, Base.muladd, a, b, _)
end
macro muladd(e)
muladd_(e)
end
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)
esc(to_muladd(add))
end
function to_muladd(add)
let mul = operands(add)[1]
if length(operands(add)) == 1
return mul
else
a, b = operands(mul)
rest = to_muladd(:( +($(operands(add)[2:end]...)) ))
return :($(Base.muladd)($a, $b, $rest))
end
end
end
operator(ex) = ex.args[1]
operands(ex) = ex.args[2:end]
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.
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
:(Base.muladd($x, $y, $(foldl((a,b)->:($b+$a), cs))))
end |> esc
end
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)
esc(to_muladd(ex))
end
function to_muladd(ex)
is_add_operation(ex) || return ex
operands = collect(zip(
to_muladd.((x->x.args[2]).(ex.args[2:end])),
to_muladd.((x->x.args[3]).(ex.args[2:end]))))
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))
end
end
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
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.