[erlang-questions] Functions with same name and varying arity using apply

Steve Vinoski vinoski@REDACTED
Thu Jan 20 04:51:03 CET 2011


On Wed, Jan 19, 2011 at 9:44 PM, Taylor Venable <taylor@REDACTED> wrote:
>
> Sorry, I could have been more clear; I'm very early in the process of
> learning Erlang and was writing my first "real" program when I
> encountered this. Basically, I want to dynamically apply functions
> which I've defined inside a module, which have the same name but take
> different numbers of arguments. If possible, I want to do it without
> exporting those "helper" functions (in this case foo/1 and foo/2).
> Perhaps a better example would be if foo/2 accepted a list of options
> that affected its behaviour, and foo/1 is just calling foo/2 with an
> empty option list:
>
> %% test.erl
>
> -module(test).
> -export([foo/1,foo/2,bar/2]).
>
> foo(A) -> foo(A, []).
> foo(A, Options) ->
>    case lists:member(double, Options) of
>        true -> A * 2;
>        false -> A
>    end.
>
> bar(_, []) -> [];
> bar(F, [X|Xs]) -> [apply(test, F, X) | bar(F, Xs)].
>
> %% END
>
> With this method, foo/1 and foo/2 have to be exported due to how
> apply/3 works (I assume; that's what the documentation says, and it
> fails if you don't export them).

If you don't export them, the compiler will think they're unused and
won't include them in the generated beam file, in which case they
obviously won't be around to call by any means.

> If I try using apply/2 then that
> takes a function, rather than an atom, so I have to do something like:
>
> bar(fun foo/1, [[1], [2, [double]]]) % from inside the module
>
> Which is not a solution because I have to declare up front whether to
> use foo/1 or foo/2, the opposite of what I'm trying to accomplish. So
> I was wondering if there was another approach to make it work.

You might consider just always passing a single list argument to foo,
so there's just one to export. It could work like this:

-module(test).
-export([foo/1, bar/2]).

foo([A]) -> foo([A, []]);
foo([A, Options]) ->
   case lists:member(double, Options) of
       true -> A * 2;
       false -> A
   end.

bar(_, []) -> [];
bar(F, [X|Xs]) -> [?MODULE:F(X) | bar(F, Xs)].

Then you could call it like this:

test:bar(foo, [[1], [2, [double]]]).

which is the same as you showed earlier. I get [1, 4] from this call,
as expected.

> The context of discovering this is that in my program I receive the
> arguments to foo (in my example: both [1] and [2, [double]]) from the
> user by way of reading terms (I think that's the right word; I'm
> reading a list of atoms and tuples and such from a file

Using file:consult/1, I hope?

> and then using
> them to direct what the program does, like a very simple specification
> of commands; like what one does in Lisp reading a sexp).

Another option along the Lisp reader line would be to read the user's
terms, and based on those terms first generate "code as data" in
Abstract Format as a set of terms, then compile it and load the
resulting beam on the fly, and finally invoke one of the newly-loaded
functions. Look up Abstract Format in the erts manual and also see the
compile module and the beam_lib module. Whether it's worth it to go to
this extent depends on how often the code would be invoked -- if
you're writing some sort of DSL that's read once and then invoked
numerous times, this approach could be worth it.

--steve


More information about the erlang-questions mailing list