Hi all,
I’ve been thinking quite a lot about partial function application and the benefits it might bring to the language the last few years. I know others have had similar thoughts since I’ve found several different implementations or proofs of concept online, but I never really saw it discussed (I’m sure it has been, somewhere, somewhen).
I am often struck by how nice Haskell’s higher-order functions like map
or fold
can look with some partial function application magic. For those unfamiliar, what is written in Erlang as fun(X) -> X < 0 end
would be written as (< 0)
in Haskell, which simply returns a new “arity 1” function that expects the missing operand.
Erlang isn’t curried, like Haskell is. Furthermore, the arity is an integral part of the function signature, so defining an add(A, B) -> A + B.
and then calling it as Increment = add(1)
would make no sense. The best idea I’ve had is to use “argument placeholders” to preserve arity.
The add/2
example could be written as Increment = add(1, _)
which would return an arity 1 fun such that 101 = Increment(100)
.
Why bother?
There are a few reasons I think it’d make a great addition to the language. First of all, it would reduce the verbosity of trivial lambda functions often encountered in lists:filter/2
and its higher-order function friends map
, foldl
, and so on. Compare the two ways of filtering all strings beginning with “Hello” from a list:
get_hellos(Greetings) ->
lists:filter(fun(E) -> lists:prefix("Hello", E) end, Greetings).
get_hellos_pfa(Greetings) ->
lists:filter(lists:prefix("Hello", _), Greetings).
It would also provide a convenient alternative syntax to fun something/1
, as in:
["HELLO", "WORLD"] = lists:map(string:uppercase(_), ["hello", "world"]).
Lastly, there has been a lot of talk about bringing over the pipe operator from Elixir. I think it has two big flaws: It obfuscates which function is being called by making an arity N function look like an arity N-1 function. And it assumes the argument needs to be piped as the first argument (or last, if the <|
reverse pipeline is added).
add(A, B, C) ->
A + B + C.
add(A, B) ->
A + B.
do_something() ->
100 |> add(50, 2). % This looks like add/2, but it's add/3.
Allowing the piping operator |>
to be used only with arity 1 funs as its second operand would eliminate both of these problems, and would even look quite elegant with partial function application. It would be possible to pipe the argument to any position, and it would be explicit which function/arity is being called.
do_something() ->
20 |> add(10, _, 30). % Very clearly arity 3 and piping the second argument.
Useful?
I’d love to hear everyone’s thoughts. Does this make absolutely no sense? Did I miss something obvious that makes this unrealistic? Does anyone have any cooler or nicer ideas to share on the topic? Any other adjacent ideas? Has it already been discussed to death?