Why Should I Care About Currying?
What is Currying
Currying comes from mathmatics. It is the transformation of a function that takes multiple arguments into a function that takes only one argument that returns a function that takes then next argument in line repeated until it can return a result.
It just means instead of passing all the arguments to a function at once, and getting a result, you can apply
some of the arguments to a function and get back a function that takes the rest of the arguments.
That means turning a function like add2Numbers
into curriedAdd2Numbers
as shown below.
function add2Numbers(x, y) {return x + y;}let six = add2Numbers(2, 4);const curriedAdd2Numbers = (x) => (y) => x + y;let five = curriedAdd2Numbers(2)(3);
If you're like me you see the example above and think "Why would I do that?". It doesn't seem so useful. I'll admit with the example of adding numbers it isn't, but currying is a powerful tool to add to your belt.
My goal is to explain currying in an aproachable way. That by the end of this you can see currying as a useful tool and be able to use it in your own code. Maybe you'll even be excited enough about it to dig further into currying and functional programming.
The power of currying comes from partial application paired with JavaScripts ability to pass functions as arguments.
For us that means 2 things. We can capture functions with some arguments already applied to create more specialized reusable functions and we can apply arguments to our functions as we pass them around until we have everything we need to call them.
Partial application
Currying let's us take a generic function apply a few arguments and capture the result. This is nice when you find your self passing the same arguments to a function over and over.
A prime example of this is a logging function with levels. In our example we have a logger that takes a level and a message. During runtime it checks the level of the message with the level of our global setting to determine wether it should show the message.
const GLOBAL_DEBUG_LEVEL = "warning";const LEVELS_RANK = {error: 1,warning: 2,info: 3,debug: 4,};function logger(level, message) {if (LEVELS_RANK[level] <= LEVELS_RANK[GLOBAL_DEBUG_LEVEL]) {console.log(`${level}: ${message}`);}}logger("info", "this seems like a bad idea");logger("warning", "you should probably stop");logger("warming", "why are you like this 😢");logger("error", "I don't want to say I told you so, but...");
By currying our logging function, and applying the level, we can create some sweet specialized logging functions and save ourselves from a possibly hard to find typo.
// by currying on the go we can get around functions where we might// not have access to change the order of the arguments, or don't want toconst logError = (message) => logger("error", message);logError("oops, forgot to declare it");// we could pass the message first if// for some reason we knew our message but not what level it should be// This isn't very clear what we're logging// But it does show that its something we could do if we wantedconst logMessage = (level) => logger(level, `check our my sweet variable`);logMessage("debug");// this is a function that transforms a normal function// into a curried function that accepts 1 or more arguments at a time// until it has enough arguments to invoke the function it was given//// order of the arguments is important in this implementationfunction curry(func) {return function currier(...args) {// if we have all of our args lets call our functionif (args.length >= func.length) {return func(...args);}// if not lets add a wrapper that can take more argumentsreturn (...moreArgs) => currier(...args, ...moreArgs);};}// curriedLogger(level)(message)const curriedLogger = curry(logger);// we apply our levellogInfo = curriedLogger("info");logWarning = curriedLogger("warning");// apply our message, and complete the function calllogInfo("because of our implementation we can pass all the arguments if we wanted too");curriedLogger("info", "This would act just like the original function");
Technically currying means we should be passing one argument at a time, but let's call it a suggestion. In Javascript there is nothing stopping us from applying more than one argument at a time as long as we call them in the right order. We can even bend the rules on argument order by wrapping our logger as we go in a function that takes the missing arguments.
Curry kind of like courier
I can't help but think about a curried function as a courier. Each time a curried function is called it is like it stopped to pick up a package. When the truck is full and it has everything it needs it can deliver.
let deliver = console.log;// courier needs to pick up 3 packages before they can deliverconst courier = (packageA) => (packageB) => (packageC) =>deliver(`${packageA}, ${packageB}, ${packageC}`);function doRoute(courier) {// each package is only available in the scope of it's post office// The Courier Starts at the state post officestatePost(courier);}function statePost(courier) {// This package is only available inside the state post officelet package = "Nintendo Switch";// the courier picks up the packagelet courierWithOnePackage = courier(package);// then goes to the next stopcountyPost(courierWithOnePackage);}function countyPost(courier) {// This package is only available inside the county post officelet package = "Animal Crossing";// the courier picks up the packagelet courierWith2Packages = courier(package);// then goes to the next stoplocalPost(courierWith2Packages);}function localPost(courier) {// This package is only available inside the county post officelet package = "Letter from Mom";// the courier picks up the final package and can finally delivercourier(package);}doRoute(courier);
In no one place was our courier able to pick up all of the packages at once. Every time they passed into a new post office they were able to grab the package from that location. When we had all the packages we were finally able to invoke the deliver function.
Let's expand on this concept even further by checking out a grocery list app.
we'll have a class called GroceryListUI
that calls 2 other functions, createItemRowWithQuantity
and createQuantityForm
, to build our grocery list app.
We want to keep our api logic in the GroceryListUI
class to make our other functions more
reusable. Maybe createQuantityForm
and createItemRowWithQuantity
live in other files and are reused
in parts of our app to handle quantity of items we have in our pantry, or items being transported.
Let's look at our components individually, then all together with only the code that deals with currying.
First we have a class called GroceryListUI
that takes an element
and a list of grocery items. It calls the other functions to build
the UI and attach it to the DOM.
// creates our grocery list ui and attached it to the item passedclass GroceryListUI {constructor(element, items) {// element we attach our list tothis.element = element;this.element.append(...this.makeGroceryList(items));}// iterates over our list of items and turns it into a list of dom elementsmakeGroceryList(items) {// map over the list of items and create the elements for each onereturn items.map((item) =>// our next functioncreateItemRowWithQuantity(item, this.updateItemQuantity));}// curried API call where we need a function that hits an item endpoint,// and id of what item to update, and the value we want to updatecurriedApiCall = (apiCall) => (itemId) => (valueChangeEvent) => {let newValue = valueChangeEvent.target.value;apiCall(itemId, newValue);};// applying our first argument to create a specialized function// for updating item QuantityupdateItemQuantity = this.curriedApiCall(putToItemQuantityEndpoint);// if we wanted to add a feature to change the name of our item we can// easily extend our curriedApiCall to do itupdateItemName = this.curriedApiCall(putToItemNameEndpoint);}
We have a function called createItemRowWithQuantity
that takes a grocery item object
and a function that calls our curried update function. It returns an element that well use as a
row in our grocery list with the item name and a form to update the quantity.
// takes a single object called item, and a function used to update an item.// returns an element that represents a row in our grocery list with a form inside it.function createItemRowWithQuantity(item, updateItemQuantity) {// create rowlet element = document.createElement("div");// add the grocery item name so we know what we needlet name = document.createElement("h2");name.textContent = item.name;// apply the item Id so we know what Item to updatelet updateThisItem = updateItemQuantity(item.id);// use the quantity and callback function to create our little formlet form = createQuantityForm(item.quantity, updateThisItem);// attach our new elements and send back our grocery itemelement.append(name, form);return element;}
createQuantityForm
takes an initial quantity value and our update function. It returns a small form element
// takes a Number Quantity and a callback function// returns a simple form with an input that holds an initial value.function createQuantityForm(quantity, updateThisItemsQuantity) {// Create our formlet form = document.createElement("form");// create the label for our inputlet label = document.createElement("label");label.textContent = "quantity";// create our inputlet input = document.createElement("input");// pass our input the value from our databaseinput.value = quantity;// attach a listener that will invoke our callback function// when the input value changes and the input loses focusinput.addEventListener("change", updateThisItemsQuantity);// put our pieces together and send them backlabel.append(input);form.append(label);return form;}
You may have noticed GroceryListUI
doesn't know what item will need to be updated, or what
the updated value is, but it does know what function to call to hit our endpoint
and update the item in the database. The element returned from createItemRowWithQuantity
knows
what item is going to be updated, and the form returned from createQuantityForm
knows what the new value is. Sounds like a great time for our courier 😉
All together now.
you can also see a working example on this codepen
class GroceryListUI {// ...// curried API call where we need a function that hits an item endpoint,// and id of what item to update, and the value we want to updatecurriedApiCall = (apiCall) => (itemId) => (valueChangeEvent) => {let newValue = valueChangeEvent.target.value;apiCall(itemId, newValue);};// lets do our first application and create a specialized function// for updating item QuantityupdateItemQuantity = this.curriedApiCall(putToItemQuantityEndpoint);// if we wanted to add a feature to change the name of our item we can// easily extend our curriedApiCall to do itupdateItemName = this.curriedApiCall(putToItemNameEndpoint);makeGroceryList(items) {// map over the list of items and create the elements for each onereturn items.map((item) =>// this arrow function calls our create function and passes the// partially applied function updateItemQuantitycreateItemRowWithQuantity(item, this.updateItemQuantity));}}function createItemRowWithQuantity(item, updateItemQuantity) {// ...// now apply the item Id and create a function specialized// for updating this specific itemlet updateThisItemsQuantity = updateItemQuantity(item.id);// use the quantity and updateThisItemsQuantity as callback function to create our formlet form = createQuantityForm(item.quantity, updateThisItemsQuantity);// ...}function createQuantityForm(quantity, updateThisItemsQuantity) {// ...// our paritally applied function now knows what endpoint to hit,// what item to update. when this event listener fires it will// get the new value and complete our function callinput.addEventListener("change", updateThisItemsQuantity);// ...}
In our class we started with a curried function called curriedApiCall
as our base.
We only knew what action to call so we applied putToItemQuantityEndpoint
and captured
its value to create updateItemQuantity
.
Next, like our courier, our curried function is passed into createItemRowWithQuantity
.
In this function our list of items has been narrowed down to 1 item, so we can apply its
id and create a function called updateThisItem
.
We pass updateThisItem
into createQuantityForm
where it's used as a
callback function to our inputs event listener.
Now when we update our values in the grocery list the event listener is called and applies our final argument, the new value, to our curried function. With that the function is fully applied and we update our database!
Summary
Currying is an incredibly useful technique you can start using right away. It let's you create reusable functions, and capture arguments as a curried function is passed into the scope of other functions.
Thanks for taking the time to read my article!
If you want to dig deeper here are some places to go next.