Skip to content

Instantly share code, notes, and snippets.

@eksperimental
Last active March 19, 2021 14:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eksperimental/0c0d253f012ad88b9b6d06a544915a07 to your computer and use it in GitHub Desktop.
Save eksperimental/0c0d253f012ad88b9b6d06a544915a07 to your computer and use it in GitHub Desktop.
Build anonymous functions with a variable length of arguments
defmodule Variadic do
@moduledoc """
Solution for https://elixirforum.com/t/defining-an-anonymous-function-of-dynamic-arity-not-variadic/38228
"""
defmacro spread_combine(h, f, g) do
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(g, :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.
Variadic.deffn args do
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
defmodule Combine do
require Variadic
def run() do
h = fn x, y -> ["h", x, y] end
f = fn x, y -> ["f", x, y] end
g = fn x, y -> ["g", x, y] end
one = 1
IO.inspect(Variadic.spread_combine(h, f, g).(one, 2, 3, 4))
IO.inspect(Variadic.spread_combine(h, f, g).({4}, {3}, {2}, {one}))
end
end
Combine.run()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment