Currying and Partial Application
Let’s talk about currying and partial application - two related but distinct topics. It might help to get familiar with lambdas and closures first before diving into this topic.
Definitions
Let’s define a function we’ll work with. It takes 3 parameters: Int, String, Boolean
and returns a String
result. It’s type signature: (Int, String, Boolean) => String
.
Function Application
Let’s first clarify function application definition:
“In mathematics, function application is the act of applying a function to an argument from its domain so as to obtain the corresponding value from its range.” Wikipedia
In programming terms function application is a regular function call or evaluation of a function given its arguments. In FP we like to view functions and their arguments as two separate pieces of data which can be combined together.
Call pipeline would look like this:
Partial Function Application
Partial application is somewhat special: you provide only some arguments and get back un-evaluated function, not the final result/value.
“Partial application (or partial function application) refers to the process of fixing a number of arguments to a function, producing another function of smaller arity.” Wikipedia.
In layman terms we just pass one or more arguments to a function (fixing/binding passed arguments) without evaluating the function. What we get as a result is that same function which requires less arguments. We have to pass number of arguments that is less than the total number of arguments (partial application), otherwise the function would get evaluated (regular function call).
Call pipeline might look like this:
This pipeline illustrates passing single argument at a time, but more than one argument can be passed if needed.
Currying
“In mathematics and computer science, currying is the technique of translating the evaluation of a function that takes multiple arguments (or a tuple of arguments) into evaluating a sequence of functions, each with a single argument.” Wikipedia
In other words we take a regular function and “curry it” to get a function that takes only one argument and returns another function that also takes only one argument, which in turn returns another function … until the last function that takes last argument and returns the final result.
Call pipeline would look like this:
Partial Function
It’s easy to confuse partially applied function with a partial function. Those are completely different concepts altogether. Check this article for more details.
Examples
It’s much easier to understand things by looking at examples as usual. This is our regular function mentioned above: (Int, String, Boolean) => String
.
Currying
In Scala you can create a curried function, i.e. perform currying by calling curried
. In some languages like Haskell all functions are curried to start with. I like Haskell’s approach because there is only one way to deal with function arguments and it fits both needs: fixing some arguments or evaluating a function.
Uncurrying
The opposite operation to currying is uncurrying.That is if we take a curried function and convert it back to a regular function with a single parameter list.
Let’s make a curried function and convert it back to normal:
As usual type signatures tell the story so we don’t need to test how it works.
Partial Application
Conclusion
Currying and partial application are quite useful techniques for achieving FP style abstractions. They are related but still different things. Both allow you to fill in function arguments progressively to eventually pass all arguments and evaluate a function.
Appendix - Python Example
Python doesn’t natively support partial function application, but has required ingredients to implement such support. Python’s functools library provides functools.partial(func[,*args][, **keywords])
function to perform partial function application. Let’s go through a similar example as above:
Our regular function we’ll mess with:
Partial Application
Notice how we provide 1 - n number of arguments and get back partially applied function. Here is a similar example where we provide arguments in multiple partial
invocations. There is essentially no difference as you can see:
Great, but how do we implement partial function application without this library ourselves? Let’s ignore some details like __doc__
and kwargs
. Let’s also not use any Python hacks with regards to how functions and arguments are defined - no monkey patching business. You can check partial
docs and implementation for more Pythonic and production grade code. Here we try to express ourselves in a straightforward way that would also work in many other languages.
Let’s break it down. Our papply
function takes 2 arguments: the function we want to use for partial application and its arguments (you can add kwargs
to it as an excercise). We return a lambda which expects even more arguments. I called them remaining arguments - rem_args
. This is the basis of partial application: allow a function to capture/bind/fix some arguments and return another function which will be expecting the remaining arguments. Finally, we pass all these arguments together to the function to execute it. Notice an important point here that since we return a lambda our func
is not going to be executed at that time. It will be applied when you pass rem_args
to that returned lambda directly (without calling papply
) even if that number of arguments is 0, i.e. ()
.
Examples:
It works as expected.
Currying
To implement curry
we need to build a sequence of lambda functions each taking a single argument - this is what nest_lambdas
does, given n
number of arguments/nesting levels. For each lambda’s argument we use partial
to pass it to our func
without evaluating it (fixing/binding an argument). Once we exhaust all arguments we just call the partially applied function which already has all arguments filled in.
These implementations are not meant to be complete, nor ready for production use. They are rather meant for illustrating the mechanisms behind currying and partial application.