The first note I’d make is that it’s ~never necessary to use string manipulation, eval
, or Meta.parse
inside a macro. One of the many reasons not to use eval
in particular is that it causes your code to be run when the macro is expanded, which is almost never what you want:
julia> macro recur(map_name, iter_num, val_input)
eval(Meta.parse(string(string(map_name, "(")^iter_num, val_input, ")"^iter_num)))
end
@recur (macro with 1 method)
julia> f() = @recur g 10 1.001
ERROR: LoadError: UndefVarError: g not defined
This fails (in a new Julia session) because eval
tries to call g(...)
when you define f
, which is definitely wrong.
Fortunately, it’s easy to avoid strings, eval
and Meta.parse
because Julia’s built-in expression objects are much easier to work with. A macro similar to yours but with none of those red flags might be:
julia> macro recur(map_name, iter_num, val_input)
expr = :($(esc(map_name))) # start with a single call
for i in 2:iter_num
expr = :($(esc(map_name)) ∘ $expr) # turn `f` into `f \circ f`
end
return quote # return an expression which computes the result
$(expr)($(esc(val_input)))
end
end
@recur (macro with 1 method)
However, I don’t really think there’s any need for metaprogamming here at all. Instead, I’d just write a function:
julia> function recur(f, iter_num, val)
@assert iter_num >= 1
output = f(val)
for i in 2:iter_num
output = f(output)
end
return output
end
recur (generic function with 1 method)
julia> g(x) = x^2
g (generic function with 1 method)
julia> recur(g, 10, 1.001)
2.7828855056543795
Or, if you want something without a loop, then we can use, well, recursion…
julia> function recur(f, iter_num, val)
@assert iter_num >= 1
if iter_num == 1
f(val)
else
f(recur(f, iter_num - 1, val))
end
end
recur (generic function with 1 method)
julia> g(x) = x^2
g (generic function with 1 method)
julia> recur(g, 10, 1.001)
2.7828855056543795