# Defining an anonymous function of dynamic arity (not variadic)

I’m attempting to walk through Software Design for Flexibility and convert the examples to Elixir where possible. In doing so I’ve run into a challenge related to function arity.

``````defmodule Combine do
n = arity(f)

the_combination = fn args ->
{fargs, gargs} = Enum.split(args, n)
h.(apply(f, fargs), apply(g, gargs))
end

the_combination
end

defp arity(f) do
:erlang.fun_info(f)[:arity]
end
end
``````

With this code, I can call `spread_combine/3` as follows.

``````h = fn x, y -> ["h", x, y] end
f = fn x, y, z -> ["f", x, y, z] end
g = fn x, y, z -> ["g", x, y, z] end

Combine.spread_combine(h, f, g).([1, 2, 3, 4, 5, 6])
# > ["h", ["f", 1, 2, 3], ["g", 4, 5, 6]]
``````

This works as expected. However, for a proper implementation of the code in the book, and I believe for optimal composability, I really want the function returned by `spread_combine/3` to take the number of arguments `n * 2` where `n` is the arity of `f`, as opposed to taking a single list of all args.

Is this possible with Elixir? I understand that variadic functions aren’t possible in Elixir. However, I’m not actually trying to create a variadic anonymous function here. In theory I should be able to define the anonymous function `the_combination` using the known arity of `f` multiplied by 2. I believe I can technically do that in a macro, using `unquote_splicing` and some lack of hygiene to still have access to all args for splitting between `f` and `g` perhaps, but I’m struggling.

If anyone more well versed in metaprogramming has any pointers I would appreciate the help. Thank you.

1 Like

Here’s a version that is close to what I want. I’m not sure how to get the arity of `f` in the macro version though.

``````defmodule Combine do
# How do I get the arity of f from the AST?
# I'm hard coding it currently.
f_arity = 2
args = Macro.generate_arguments(f_arity * 2, __CALLER__.module)

quote do
the_combination = fn unquote_splicing(var!(args)) ->
{fargs, gargs} = Enum.split(unquote(args), unquote(f_arity))

unquote(h).(apply(unquote(f), fargs), apply(unquote(g), gargs))
end

the_combination
end
end

# This doesn't work when f is AST
def arity(f) do
:erlang.fun_info(f)[:arity]
end
end
``````

This hardcoded arity version can be exercised with the following.

``````require Combine

h = fn x, y -> ["h", x, y] end
f = fn x, y -> ["f", x, y] end
g = fn x, y -> ["g", x, y] end

Combine.spread_combine(h, f, g).(1, 2, 3, 4)
``````
1 Like

I’m not certain it’s possible to do that - for instance, in the example shown the actual value of `f` passed to the macro is something like `{:f, [line: NNN], nil}`. The arity isn’t fully knowable until runtime, especially since functions can appear from the void with `:erlang.binary_to_term/1`:

``````# on one machine
binary_data = :erlang.term_to_binary(fn x, y -> IO.inspect(x, label: y) end)

# send binary_data over the network to another node, store it on disk, etc
# on the other machine
f = :erlang.binary_to_term(binary_data)
# f is now an arity-2 anonymous function
``````
2 Likes

Interesting exercise, and a tricky one to implement.
It would be easily solvable, but you cannot use `unquote_splicing` to pass arguments to special forms (If I understand correctly the issue). So you need to build by AST by hand.
Here’s a solution
Build anonymous functions with a variable length of arguments · GitHub

``````defmodule Variadic do
@moduledoc """
"""

quote bind_quoted: [h: h, f: f, g: g, module: __CALLER__.module],
location: :keep do
{:arity, f_arity} = Function.info(f, :arity)
{:arity, g_arity} = Function.info(f, :arity)
args = Macro.generate_arguments(f_arity + g_arity, module)
{f_args, g_args} = Enum.split(args, f_arity)

# The trick here is in order to bypass the limitation of using unquote_splicing to pass arguments to special forms,
# we build the AST "by hand" and evaluate it.
h.(apply(f, f_args), apply(g, g_args))
end
end
end

@doc false
# Builds the Kernel.SpecialForms.fn/1 AST
def deffn(args, do: body) do
fn_ast =
{:fn, [],
[
{:->, [], [args, body]}
]}

Code.eval_quoted(fn_ast) |> elem(0)
end
end
``````
2 Likes

Thank you @eksperimental! I would not have guess this one.

I like how your code doesn’t assume `f` and `g` have the same number of args either. I also forgot that Elixir had `Function.info` wrapping up `:erlang.fun_info`.

Note that there’s a typo with the second `Function.info` call passing `f` instead of `g`.

@al2o3cr I forget to keep in mind the dynamic nature of how even functions could be deserialized and invoked. I need to think more to fully bake in my mind the implications. Thanks for the response.

1 Like

You are welcome!

That was a last minute change. I have fixed it in the gist, but I think since my post was chosen as the answer it does not allow me to update it.

I use `Code.eval_quoted/3` for a more dynamic behavior:

``````  def compose(f, g) do
{:arity, arity} = Function.info(g, :arity)

args = for _ <- 1..arity, do: Macro.unique_var(:arg, __MODULE__)

fn_ast =
quote do
fn unquote_splicing(args) ->
var!(f).(var!(g).(unquote_splicing(args)))
end
end

{compose_fn, _} = Code.eval_quoted(fn_ast, f: f, g: g)

compose_fn
end
``````
2 Likes

I tweaked it to make it work

I like your solution better because it is a function, not a macro.
Very interesting. Thank you @dsdshcym

I’m a little late to this, but this is what I did when I needed to make an anonymous function with a dynamic arity:

`````` defp make_lambda(arity, fun, args, type) do
arg_list = for arg <- 1..arity do
last_arg = if arg != arity do ", " else "" end
"arg" <> to_string(arg) <> last_arg
end

arg_list = List.to_string(arg_list)
fun_cmd = "fn(" <> arg_list <> ") -> Curry.do_generate_next(fun, args ++ [" <> arg_list <> "], type) end"

{lambda, _} = Code.eval_string(fun_cmd, fun: fun, args: args, type: type)

lambda
end
``````