We can use macro to generate code like f.(f, …)
sum =
make_rec(fn
[], result -> result
[num | next], result -> __REC__(next, num + result)
end)
IO.puts(sum.([1, 2, 3], 0))
Here is the implementation
defmodule Rfn do
defmacro make_rec({:fn, ctx, patterns}) do
arity = get_arity(patterns)
new_patterns = patterns |> Enum.map(&insert_rec_fn/1)
ast = {:fn, ctx, new_patterns}
quote do
f = unquote(ast)
make_fn_arity(f, unquote(arity))
end
end
defmacro make_fn_arity(f, arity) do
{:&, [], [{{:., [], [f]}, [], [f | 1..arity |> Enum.map(&{:&, [], [&1]})]}]}
end
defp insert_rec_fn({:->, _, [_arg_list, _]} = pattern) do
{pattern, mod?} =
Macro.postwalk(pattern, false, fn
{:__REC__, _, arg_list}, _ when is_list(arg_list) ->
re = {{:., [], [{:rec__, [], nil}]}, [], [{:rec__, [], nil} | arg_list]}
{re, true}
other, mod? ->
{other, mod?}
end)
arg_name =
if mod? do
:rec__
else
:__REC__
end
update_in(pattern, [Access.elem(2), Access.at(0)], &[{arg_name, [], nil} | &1])
end
defp get_arity([{:->, _, [arg_list, _]} | _]) do
length(arg_list)
end
end