An example says more than a thousand words:
> Times2 = pa:bind(fun erlang:'*'/2, 2).
#Fun<pa.17.35850360>
> lists:map(Times2, [1,2,3]).
[2,4,6]
Take an example from MongooseIM (actually a legacy leftover from ejabberd) - this inline fun is 55 lines long!
The fun code has problems with indentation, since the body of the
inline fun should be indented more than the outer code and using case
s
and if
s increases the indentation level even more.
This fun is complex and not easily traceable, because we don't know its real
name (at least without referring to erlc -S
) - in case of a fun this
long, its logic might not be obvious, so tracing and seeing the return
value might be really helpful.
A more general example of the same problem looks like this:
do_something_on_a_list_of_items(ListOfItems) ->
SomeVar1 = get_some_var1(),
SomeVar2 = get_some_var2(),
SomeVar3 = get_some_var3(),
lists:map(fun (Elem) ->
%% very long closure using variables
%% SomeVar1, SomeVar2, SomeVar3 closed over
%% from the outer environment
end, ListOfItems).
I hope you're convinced that refactoring is necessary.
do_something_on_a_list_of_items(ListOfItems) ->
SomeVar1 = get_some_var1(),
SomeVar2 = get_some_var2(),
SomeVar3 = get_some_var3(),
lists:map(mk_step(SomeVar1, SomeVar2, SomeVar3), ListOfItems).
mk_step(SomeVar1, SomeVar2, SomeVar3) ->
fun (Elem) ->
do_something_with_one_item(SomeVar1, SomeVar2, SomeVar3, Elem)
end.
do_something_with_one_item(SomeVar1, SomeVar2, SomeVar3, Elem) ->
%% still a long function using variables
%% SomeVar1, SomeVar2, SomeVar3
%% passed in as arguments
...
The end result is functionally the same,
but do_something_with_one_item/4
is perfectly traceable and doesn't suffer
from pathological indentosis.
However, mk_step/3
from above is just boilerplate.
The same could be achieved without proliferation of similar mk_sth/3
and mk_sth_else/5
functions throughout the codebase with function partial
application. Which Erlang lacks!
Thankfully, this library fixes this nuisance by providing a quite convenient (as far as the syntax permits) implementation:
do_something_on_a_list_of_items(ListOfItems) ->
SomeVar1 = get_some_var1(),
SomeVar2 = get_some_var2(),
SomeVar3 = get_some_var3(),
lists:map(pa:bind(fun do_something_with_one_item/4,
SomeVar1, SomeVar2, SomeVar3), ListOfItems).
do_something_with_one_item(SomeVar1, SomeVar2, SomeVar3, Elem) ->
%% still a long function using variables
%% SomeVar1, SomeVar2, SomeVar3
%% passed in as arguments
...
Of course, map/2
and foreach/2
require unary functions,
but foldl/3
a binary one and you might come up with a number of
functions with even wilder requirements!
You can define the maximum supported arity by defining the value of the
max_partial_arity
macro during compilation.
Defining it in your rebar.config
file is a pretty convenient way to do so.
The default value is 15.