Functional JavaScript: Currying Functions
When I first heard the term ‘function currying’ in JavaScript, I immediately thought, ‘Wow, a program that can drain three’s’.
But then I remembered that we are talking about functional programming and not functional jump shots.
In the simplest terms, function currying involves breaking up a function that takes several arguments into a high order function that returns functions that will receive the additional arguments to obtain a desired value in the future. Once all of the inner functions have been invoked, the final function will give us the desired outputted value.
Enough words. Let’s look at code!
Here, we have function animalDescription that takes in three arguments as strings: name, adjective, and animal. It then outputs a string concatenating those arguments. Let’s curry this function up!
curriedAnimalDescription
is a higher order function that takes in a string of a name as an argument. It’s return value is a function that takes in a string of an adjective. That function’s return value is another function that takes in a string of an animal as an argument and returns a concatenated string of all of our arguments. Because functions are first class citizens in JavaScript and can be returned as values of other function, each return function inside curriedAnimalDescription
is bound to the lexical environment in which they are declared. This is called a closure. Therefore the first function returned from invoking curriedAnimalDescription
will always be bound to the value of name
passed into curriedAnimalDescription
. The same principle applies to all of our functions that will eventually be returned from curriedAnimalDescription
. So by the time we will invoke our final enclose function, that function will be bound to arguments passed in for name
and adjective
once both of those enclosing functions are invoked. Before we go further, let’s invoke some of these functions, assign their return values to a const
and check out their return values.
Above, we assign const riley
to the return value of curriedAnimalDescription(‘Riley’)
which is a function that is bound to the lexical environment in which name
, as written in our in our original function declaration, is assigned to the string value ‘Riley’
.
We then invoke riley
with the argument 'good'
and save the returned value to a variable called goodRiley
. The value of goodRiley
is a function that is now bound to the variable name
pointing to the string 'Riley'
and the variable animal
pointing to the string 'good'
.
Finally we invoke goodRiley
with the string 'dog'
as its argument and assign its return value to const rileyDescription
. This value is a concatenated string that is still bound to our name
and adjective
values that were invoked in earlier functions. Therefore we are able to successfully return the string: “Riley is a good dog!”
, which he is.
Why is this useful? Let’s take a look using another higher order function built on the Array prototype: filter.
Consider the following arrays of objects. These are a list of animals that are available for adoption and a list of people that are available to adopt them!
We can see that in each array, there are objects representing people and animals. A common key between the animal objects and people objects is availability
. Let’s write a generic currying function that will give us some flexibility to filter each array based on the property availability
of each object and its value, either true
of false
.
Our hasProperty
function is flexible enough that we are able to declare little bits of it at a time. Let’s set a variable that will be bound to the property availability
.
Now, let’s declare a const isAvailable
that points to available(true)
. Its return value will be a function that takes an object as an argument and will return a boolean of true
of false
if the object’s available
property is equal to the boolean true
which in turn has already been bound to value
from our hasProperty(‘available’)
invocation.
Our isAvailable
function can now be passed around as a callback to filter on both the animals
and people
arrays since both objects have keys of available
that point to booleans.
As the filter method iterates through our object array, it passes each object to isAvailable
. isAvailable
is already bound to both the availability
property
and the value
of true
. So, the filter method uses isAvailable
as a callback to construct a new array of both our animals and our people that are available for adoption and to adopt!
We have composed currying functions that are concise and can be reused later in our program. For example, if we had a list of dog house objects for sale with the key of availability
pointing to a boolean telling whether the dog house was in stock or not, we could call filter on the dog house array and pass isAvailable
yet again for a list of all dog houses that are available for purchase. We can reuse the tight and cleanisAvailable
function if there are other animal objects in an array such as cats, goldfish and emus who are all fighting for adoption.
Our hasProperty
function is generic enough that we can use it with any property and any value that we may come across while building our application. Reusable === good.
There are many other uses for currying functions. The examples are here are a basic way to understand the power of currying! Currying functions allows programs to manage variables, bind functions to specific contexts, and to delay executions of functions if an application is waiting for a response from another part of the application by binding values to functions that we do currently have.
Big shout out to Fun Fun Function and Kristina Brainwave for their awesome explanations!