[erlang-questions] Automatic currying

89 views
Skip to first unread message

Siraaj Khandkar

unread,
Apr 30, 2015, 4:43:05 PM4/30/15
to erlang-q...@erlang.org
One of the things I often wish for, when using Erlang, is staged
application of any function without having to manually wrap it in stage
funs (which you get in languages that feature curried-by-default functions).

The other day it occurred to me that the ability to get fun arity info
and to apply arguments as a list was all that was needed to get very,
very close to the behavior I wanted. Voila:

-module(function).
-export([curry/1]).

curry(F) ->
Info = erlang:fun_info(F),
{value, {arity, Arity}} = lists:keysearch(arity, 1, Info),
curry(F, [], Arity).

curry(F, Args, 0) ->
apply(F, lists:reverse(Args));
curry(F, Args, Arity) ->
fun (X) -> curry(F, [X | Args], Arity - 1) end.

In action:

$ erl
1>
1> Nums = lists:seq(1, 10).
[1,2,3,4,5,6,7,8,9,10]
2>
2> Plus = function:curry(fun (X, Y) -> X + Y end).
#Fun<function.0.4634292>
3>
3> lists:map(Plus(1), Nums).
[2,3,4,5,6,7,8,9,10,11]
4>
4> lists:map(Plus(3), Nums).
[4,5,6,7,8,9,10,11,12,13]
5>
5> lists:map(Plus(5), Nums).
[6,7,8,9,10,11,12,13,14,15]
6>
6> Fold = function:curry(fun lists:foldl/3).
#Fun<function.0.4634292>
7>
7> AddTo = Fold(fun (X, Y) -> X + Y end).
#Fun<function.0.4634292>
8>
8> AddTo0 = AddTo(0).
#Fun<function.0.4634292>
9>
9> AddTo100 = AddTo(100).
#Fun<function.0.4634292>
10>
10> AddTo0(Nums).
55
11> AddTo100(Nums).
155
12>
12> AddTo100(lists:seq(4, 78)).
3175
13>
_______________________________________________
erlang-questions mailing list
erlang-q...@erlang.org
http://erlang.org/mailman/listinfo/erlang-questions

Pierre Fenoll

unread,
Apr 30, 2015, 7:46:26 PM4/30/15
to Siraaj Khandkar, erlang-questions@erlang.org Questions
I'd like to have a shorthand syntax:

1> Print = fun io:format("lookee here: ~p\n")/1.
#Fun<function.0.2345234>
2> Print([ stuff ]).
lookee here: stuff
ok
3>

Print would actually be sugar for
Print = fun (X) -> io:format("lookee here: ~p\n", X) end.
with all the rules concerning funs applying in the same way.


Cheers,
-- 
Pierre Fenoll

Richard A. O'Keefe

unread,
Apr 30, 2015, 8:47:11 PM4/30/15
to Erlang Questions

On 1/05/2015, at 8:42 am, Siraaj Khandkar <sir...@khandkar.net> wrote:

>
> curry(F) ->
> Info = erlang:fun_info(F),
> {value, {arity, Arity}} = lists:keysearch(arity, 1, Info),
> curry(F, [], Arity).

You do know that you can write
{arity, Arity} = erlang:fun_info(F, arity) ?

>
> curry(F, Args, 0) ->
> apply(F, lists:reverse(Args));
> curry(F, Args, Arity) ->
> fun (X) -> curry(F, [X | Args], Arity - 1) end.

This is where it gets hairy. The thing is that in a language
like Haskell or Clean, there is no difference between

f

and

f {- applied to no arguments -}

In Erlang, there is a big difference between

F

and

F()

What if I have

F = fun (X) -> ... end

and I want

(curry(F))(X)

to give me fun () -> ... X ... end?

Having

curry(F, Args, 0) ->
Rev = lists:reverse(ARgs),
fun () -> apply(F, Rev).

would allow people to invoke the result or not, as they chose.

I suggest *not* doing any of this.
Haskell tends to involve stuff like (`f`y) for
providing the second argument, which looks distinctly
odd when f has more than 2, and don't let's mention
`flip' and its relatives.

Why do I suggest not doing this?
First, as already mentioned, the fact that Erlang
functions are strict and effectful means that the
distinction between F and F() is vital.
Second, this isn't really how Erlang is _meant_ to
be used, so unlike Haskell, the compiler is not
going to put much effort into making this less slow.
Most important, other people aren't going to expect
to read Erlang code written this way, so it's not
going to be as maintainable as possibly longer code
that's more direct.

Siraaj Khandkar

unread,
May 1, 2015, 2:03:16 AM5/1/15
to erlang-q...@erlang.org
On 4/30/15 8:46 PM, Richard A. O'Keefe wrote:
>
> On 1/05/2015, at 8:42 am, Siraaj Khandkar <sir...@khandkar.net> wrote:
>
>>
>> curry(F) ->
>> Info = erlang:fun_info(F),
>> {value, {arity, Arity}} = lists:keysearch(arity, 1, Info),
>> curry(F, [], Arity).
>
> You do know that you can write
> {arity, Arity} = erlang:fun_info(F, arity) ?

Did not. Nice!

>> curry(F, Args, 0) ->
>> apply(F, lists:reverse(Args));
>> curry(F, Args, Arity) ->
>> fun (X) -> curry(F, [X | Args], Arity - 1) end.
>
> This is where it gets hairy. The thing is that in a language
> like Haskell or Clean, there is no difference between
>
> f
>
> and
>
> f {- applied to no arguments -}
>
> In Erlang, there is a big difference between
>
> F
>
> and
>
> F()

This comparison conflates explicit staged application with implicit
delayed evaluation. Semantically, the unit is still there even in
Haskell, as the last argument of the thunk, it's just that it is
implicit and when it gets (ostensibly) applied is not up to you (yes, I
know there're ways to force it, but that is besides this point).

Another strict language would be a better choice for comparison. Using
SML, the equivalents would be:

Value:
SML: f
Erlang: F

Thunk activation:
SML: f ()
Erlang: F()

Very similar, with the unfortunate difference of () not being a value
in Erlang.

> What if I have
>
> F = fun (X) -> ... end
>
> and I want
>
> (curry(F))(X)
>
> to give me fun () -> ... X ... end?

If you actually do want the final output to be a thunk, then that thunk
has to be returned by the original, non-curried, function. i.e. instead of

fun (X) -> .. end

you can have

fun (X) -> fun () -> .. end end

or, alternatively, just use a value of your choice as an ostensible unit:

fun (X, unit) -> .. end
fun (X, {}) -> .. end

So the last argument would have no meaning other than activation. Again,
_if_ this is what you want - you ask for it explicitly.

> Having
>
> curry(F, Args, 0) ->
> Rev = lists:reverse(ARgs),
> fun () -> apply(F, Rev).

This breaks the contract of currying - it does something I did not ask
it to do.

> would allow people to invoke the result or not, as they chose.

The opposite - forcing a thunk on them would take the choice away from
them (besides just breaking the contract).

> I suggest *not* doing any of this.
> Haskell tends to involve stuff like (`f`y) for
> providing the second argument, which looks distinctly
> odd when f has more than 2, and don't let's mention
> `flip' and its relatives.
>
> Why do I suggest not doing this?
> First, as already mentioned, the fact that Erlang
> functions are strict and effectful means that the
> distinction between F and F() is vital.

Addressed above. Currying has nothing to do with laziness.

> Second, this isn't really how Erlang is _meant_ to
> be used,

Erlang was not "meant" to be used in any of the ways it is being used
right now. lambdas were added relatively late (which means it was not
meant for any of the staple FP list processing functions like
map/filter/fold).

> so unlike Haskell, the compiler is not
> going to put much effort into making this less slow.

Sure. We're no worse off here than with any other use of funs. If
performance is an issue in some area of the code, than you optimize as
needed. There's no conflict.

Remember, I'm not advocating for anything radical, like overlaying all
existing functions with curry/1 and only using the curried form (that
would be a horrible user experience, given the Erlang syntax). I'm
simply sharing a technique which can be used when you do want it,
explicitly and judiciously.

> Most important, other people aren't going to expect
> to read Erlang code written this way, so it's not
> going to be as maintainable as possibly longer code
> that's more direct.

As mentioned above - I'm not saying to curry all (or even most)
functions this way - I'm saying that if/when one desires something like
this - this is a convenient technique :)

Alex Wilson

unread,
May 1, 2015, 7:08:37 AM5/1/15
to Siraaj Khandkar, erlang-q...@erlang.org
On Thu, 2015-04-30 at 16:42 -0400, Siraaj Khandkar wrote:
> One of the things I often wish for, when using Erlang, is staged
> application of any function without having to manually wrap it in stage
> funs (which you get in languages that feature curried-by-default functions).

Have you seen the Scheme-style "cut" parse_transform in erlando?
https://github.com/rabbitmq/erlando#cut

It's not currying, but you can often use it to meet a lot of the same
needs.

Siraaj Khandkar

unread,
May 2, 2015, 12:23:11 AM5/2/15
to Pierre Fenoll, erlang-questions@erlang.org Questions
On 4/30/15 7:45 PM, Pierre Fenoll wrote:
> I'd like to have a shorthand syntax:
>
> 1> Print = fun io:format("lookee here: ~p\n")/1.
> #Fun<function.0.2345234>
> 2> Print([ stuff ]).
> lookee here: stuff
> ok
> 3>
>
> Print would actually be sugar for
> Print = fun (X) -> io:format("lookee here: ~p\n", X) end.
> with all the rules concerning funs applying in the same way.

Maybe like this:

1>
1> Print = function:curry(fun (Prefix, Term) -> io:format("~s~p~n",
[Prefix, Term]) end).
#Fun<function.0.4634292>
2>
2> PrintNaked = Print("").
#Fun<function.0.4634292>
3> PrintLookee = Print("lookee here: ").
#Fun<function.0.4634292>
4>
4> PrintNaked(queue:new()).
{[],[]}
ok
5>
5> PrintLookee(queue:new()).
lookee here: {[],[]}
ok
6>

e...@bestmx.net

unread,
May 2, 2015, 4:44:01 PM5/2/15
to erlang-q...@erlang.org
> I suggest *not* doing any of this.
> Haskell tends to involve stuff like (`f`y) for
> providing the second argument, which looks distinctly
> odd when f has more than 2, and don't let's mention
> `flip' and its relatives.
>
> Why do I suggest not doing this?
> First, as already mentioned, the fact that Erlang
> functions are strict and effectful means that the
> distinction between F and F() is vital.
> Second, this isn't really how Erlang is _meant_ to
> be used, so unlike Haskell, the compiler is not
> going to put much effort into making this less slow.
> Most important, other people aren't going to expect
> to read Erlang code written this way, so it's not
> going to be as maintainable as possibly longer code
> that's more direct.

i want to add,
according to my experience,
currying tends to make code really (i mean _really_) unreadable.

i can not formally explain this effect,
on the contrary,
i love currying, and i would expect it to work nice,
but in real life:
it is very hard to _DETECT_ a partial application
during code reading.

so i consider "massive", wordy, explicit erlang's style
of partial application a huge advantage.

also i vote +1 to the Richard's observation regarding argument order
in the light of currying.

Richard A. O'Keefe

unread,
May 4, 2015, 12:48:46 AM5/4/15
to e...@bestmx.net, erlang-q...@erlang.org
The advantage of the proposed currying function is that
it is just library code.

Let me show you what Eiffel does these days.

<Eiffel fun> ::= 'agent' [<agent receiver> '.' ]<feature name>
['(' <agent argument> {',' <agent argument>}* ')']

That is, an "agent expression" looks pretty much like a normal
method call with the keyword 'agent' in front of it.

If there is no agent receiver, the Current object is the
receiver.

<agent receiver> ::= <primary> | '{' <class name> '}'.

In the first case, a call of the agent will use the
specified object; in the second case, the receiver will
be a (the first) parameter of the agent and {C} gives its
type.

<agent parameter> ::= <expression> | '?' | '{' <class name> '}'

Agent parameters that are expressions are evaluated at the
time the agent expression is evaluated. Their values are
remembered as part of the closure. Agent parameters that
are question marks are parameters of the agent. The
compiler can figure out the type of such parameters, but if
you want to make it clear to humans reading the code, you
can use the {C} format if you want to.

So given a method foo of three arguments, you can write

agent r.foo(x, y, z) -- 0 arguments
agent r.foo(x, y, ?) -- \
agent r.foo(x, ?, z) -- these four have
agent r.foo(?, y, z) -- 1 argument
agent {C}.foo(x, y, z) -- /
agent r.foo(x, ?, ?)
agent r.foo(?, y, ?)
agent r.foo(?, ?, z)
agent {C}.foo(x, y, ?)
....
agent r.foo(?, ?, ?) -- 3 arguments
agent {C}.foo(?, ?, ?) -- 4

You can't *re-order* arguments, but you can leave any
arguments you want open, not just trailing ones, as
currying does.

Could this syntax be adapted to Erlang?
Sure. 'agent' -> 'fun', receiver -> module, '.' -> ':'.
fun f(X, ?) and fun f(?, Y) would make perfect sense,
and fun f(X, Y) would be clearly distinct from f(X, Y)
just as agent r.f(x, y) is a parameterless procedure
different from f(x, y) in Eiffel.

Currying fits nicely into languages where every function
has EXACTLY ONE argument, like ML, Miranda, F#, Clean, ...
Erlang is not such a language, and neither is Eiffel,
which is why if we *did* want a currying construct for
Erlang, Eiffel's agent expressions might be a better fit.

For what it's worth, I've got some 'currying' methods
in my Smalltalk, so that
(aBlock bindFirst: x) value: y
=
aBlock value: x value: y.
Having written them, I've found them remarkably useless.
Reply all
Reply to author
Forward
0 new messages