Search code examples
erlangarity

Erlang generate anonymous function of an arbitary arity?


Is it possible to write a function that returns an anonymous function of a specified arity? I'd like to be able to generate a function that can be passed to meck:expect/3 as the third argument so I can dynamically mock existing functions of any arity?

I've done quite a bit of searching and it seems like the only way to solve this is by hardcoding things like this:

gen_fun(1, Function) ->
    fun(A) -> Function([A]) end;
gen_fun(2, Function) ->
    fun(A, B) -> Function([A, B]) end;
...

Solution

  • It's not pretty, but you can use the same trick as the shell and build your functions from the ground up:

    -module(funny).
    
    -export([gen_fun/1, gen_fun/2]).
    
    -spec gen_fun(function()) -> function().
    gen_fun(Function) ->
        {arity, Arity} = erlang:fun_info(Function, arity),
        gen_fun(Arity, Function).
    
    -spec gen_fun(non_neg_integer(), function()) -> function().
    gen_fun(Arity, Function) ->
        Params = [{var, 1, list_to_atom([$X| integer_to_list(I)])} || I <- lists:seq(1, Arity)],
        Anno = erl_anno:new(1),
        Expr =
            {'fun',
             Anno,
             {clauses, [{clause, Anno, Params, [], [{call, Anno, {var, Anno, 'Function'}, Params}]}]}},
        {value, Fun, _Vars} = erl_eval:expr(Expr, [{'Function', Function}]),
        Fun.
    

    Then, in the shell…

    1> F = funny:gen_fun(fun io:format/2).
    #Fun<erl_eval.43.40011524>
    2> F("~ts~n", ["😑"]).
    😑
    ok
    3> F1 = funny:gen_fun(fun io:format/1).
    #Fun<erl_eval.44.40011524>
    4> F1("😑~n").
    😑
    ok
    5>