I am building a macro that accepts a function. Depending on the arity of the function, I need the macro to generate different code. So far I have come up with this:
defmacro mymacro(fun) do
arity =
case fun do
{:&, _, [{:/, _, [_, arity]}]} ->
arity
{:fn, _, [{:->, _, [args, _]}]} ->
length(args)
end
case arity do
1 ->
quote do
fun.(:arg1)
end
2 ->
quote do
fun.(:arg1, :arg2)
end
end
end
It works like this:
mymacro(fn a, b -> {a,b} end)
mymacro(fn a -> {a} end)
mymacro(&Atom.to_string/1)
But it has 3 problems:
- it does not work with anonymous functions defined using the
&
shorthand. I do not see a reliable way to find the arity of such functions - It would not work if the argument passed to the macro is another macro because that would be just arbitrary AST
- It would not work if the argument passed to the macro has to be evaluated at runtime because that would be just arbitrary AST
I can live with the limitation #3.
For the other 2, I could use Code.eval_quoted
to obtain the function and determine the arity, but AFAIK that’s considered bad practice and should be avoided
Another option would be to determine the arity at runtime, like this
defmacro mymacro2(fun) do
quote do
f = unquote(fun)
{:arity, arity} = Function.info(f, :arity)
case arity do
1 ->
f.(:arg1)
2 ->
f.(:arg1, :arg2)
end
end
end
but in this case dialyzer gives warnings about unmatched cases
Of course these macros are just examples to demonstrate the problem, and my actual macros are different. And in my particular case, I can think of some workarounds. So it just became kind of academic question for me, is there a generic good way to find out the arity of function passed as macro parameter at compile time?
Any hints?