Last active
March 19, 2021 14:13
-
-
Save eksperimental/0c0d253f012ad88b9b6d06a544915a07 to your computer and use it in GitHub Desktop.
Build anonymous functions with a variable length of arguments
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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