DEV Community

Cover image for Explaining currying to myself
Damien Cosset
Damien Cosset

Posted on • Originally published at damiencosset.com

Explaining currying to myself

Introduction

Well, time to teach myself something I don't understand again...
In this article, we will explore a technique called currying. Currying is another concept in functional programming. It goes as follow: when a function expects several arguments, we break it down into successive chained functions that each take a single argument.

We reduce the arity of each function to one ( arity is the arguments length of a function ). Let's see some examples.

Note: You should probably understand closures before going further. Maybe with this?

Simple currying

Let's start with a non-curried example. We want to calculate the sum of the arguments given to a function:

const sum = (...args) => {
  let sum = 0
  for( let i = 0; i < args.length; i++ ) {
    sum += args[i]
  } 
  return sum
}

sum( 1, 2, 3, 4, 5, 6, 8) //29

A curried version of the sum function would look like so:

sumCurry(1)(2)(3)(4)(5)(6)(8) //29

How would we implement currying then? Well, let's try something like this:

const curried = 
  ( func, arity = func.length, nextFunc ) =>
    (nextFunc = prevArgs => 
      nextArg => {
        let args = prevArgs.concat( [nextArg] )

        if( args.length >= arity ){
          return func( ...args )
        } 
        else {
          return nextFunc( args )
        }
      })( [] )

const sumCurry7 = curried( sum, 7)
sumCurry7(1)(2)(3)(4)(5)(6)(8) // 29

Ok, let's break the curried function down a bit.

It takes 2 arguments, the first is the function to be called at the end ( sum in our case ), the second is the arity we expect ( the number of arguments we want before calling sum ).

This function returns a IIFE ( Immediately Invoked Function Expression ). This IIFE takes a empty array as a parameter on its first call. It returns another function with the next argument in our curry sequence as an argument and the function adds it to our collection of arguments. On the first call, our array is empty so the variable args is equal to [ 1 ].

We check if we have enough arguments to call our original function. If not, we create another curried function and keep collecting arguments. If we do, we call sum with all of our arguments.

Another example

Let's imagine we are in a restaurant. We can order one dish and a dessert. We have four different components to our order, the customer's id, its table number, the dish and the dessert.

Our curried function would look like so:

curriedOrder(customerId)(tableNumber)(dish)(dessert)

Let's create a function that just returns the order:

const displayOrder = (...infos) => {
  let order = `Customer ${infos[0].toUpperCase()} at table ${infos[1]} wants ${infos[2]} ${infos[3] ? `and ${infos[3]} for dessert.` : '.'}`
  return order
}

Nothing fancy here. Let's curry this with the function we created earlier:

const mealOneDish = curried( displayOrder, 3 )
const mealWithDessert = curried( displayOrder, 4)

const orderOne = mealOneDish('Joe')(3)('cheeseburger')
// Customer JOE at table 3 wants cheeseburger .

const orderTwo = mealWithDessert('Sarah')(6)('fries')('waffles')
// Customer SARAH at table 6 wants fries and waffles for dessert.

Why use currying?

The first reason to use a technique like currying is that you can separate in your codebase when and where your arguments are specified. You may not have all the arguments necessary to call the sum function right away, or all the order informations to print them out.

The client gets in the restaurant, you don't know where she is going to sit yet. Her name is Johanna.

const johanna = mealWithDessert('Johanna')

She walks around and finally decides to sit at the table number 7.

const johannaTable7 = johanna(7)

The waiter takes her order.

const johannaT7Order = johannaTable7('nuggets')

  • Would you like a dessert?
  • No, thank you
johannaT7Order() // Customer JOHANNA at table 7 wants Nuggets .
  • Hold on, I changed my mind. I'll have an ice cream for dessert.
johannaT7Order('ice cream') //Customer JOHANNA at table 7 wants Nuggets and ice cream for dessert.

The same issue could be applied if we wanted to calculate a sum with our earlier function.

const sumCurry4 = curried( sum, 4) 
// I want to calculate the sum of four numbers, I don't know them yet.

const firstNumber = sumCurry4(3) // Ok first one

//...Doing other important stuff ...

const secondAndThird = firstNumber(5)(20) // I need one more

//...Feeding the cat...

const final = secondAndThird(2) // 3 + 5 + 20 + 2 = 30

If we don't have the number of arguments required yet, our curry returns a function. We only call the original function when all the arguments are there.

The second reason to work with curried functions is because it is a lot easier to work with unary ( single argument ) functions. Composition is more effective with single argument functions, and a key component of functional programming.

I also like how readable it makes the code, but it might be a personal preference.

Well, that was an introduction about currying. I hope I've been clear enough on a subject I'm still exploring myself. I guess I could have gone a bit further with my curried implementation and curry that also... Any feedback/rectification is welcome!

Top comments (6)

Collapse
 
mikkpr profile image
Mikk Pristavka

Great article, Damien! There are just a few things I noticed in the definition of the curried function.
return fn( ...args ) should be return func( ...args ), I think.
Also, I don't quite get why you're defining nextFunc as an argument to the curried function. Is it just to declare the variable so that you wouldn't have to explicitly declare it inside the arrow function body and leave out the brackets? Just a bit unclear to me.

Collapse
 
damcosset profile image
Damien Cosset • Edited

Oh, you are right, it should be func and not fn. Thank you, I updated the code.

You know what, I actually have an issue with that. If I remove nextFunc from the arguments, I have an TypeError when I call my curried function.

But, when I implement my same curried function using the function keyword :

function curried(func,arity = func.length) {
  return (function nextCurried(prevArgs){
    return function curried(nextArg){
      let args = prevArgs.concat( [nextArg] );

      if (args.length >= arity) {
        return func( ...args );
      }
      else {
        return nextCurried( args );
      }
    };
  })( [] )
}

I don't need to specify this third argument. My guess is ( and it's only a guess so far ) that the arrow functions somehow changes something in my implementation. I'll dig a bit more. Good question!

Collapse
 
mikkpr profile image
Mikk Pristavka • Edited

And don't forget to also change the default value of arity from fn.length to func.length :)

Collapse
 
eljayadobe profile image
Eljay-Adobe

You can also do currying directly without having a curry-fier.

For example...

const cf = (a) => (b) => (c) => (d) => { return a + b * c - d; }

One of the things that I like about "functional-first" functional programming languages is that currying is the norm (as is immutability, concise syntax, pattern matching, non-nullable references, tail recursion, emphasis on purity), and you'd have to go out of your way to not do currying. For example, in F#:

let cf a b c d = a + b * c - d

That function is curried. And partial application is equally easy and straightforward:

let pa x y = cf 10 20

Collapse
 
damcosset profile image
Damien Cosset

It does take more discipline to use the functional side of Javascript. I think I added a curry-fier because I wanted to play around with the number of arguments. But it does make it less readable.

Collapse
 
bobbypriambodo profile image
Bobby Priambodo

The curried function with all its parens looks a bit too codegolf-ish :D I think there are opportunities to make it clearer.

But overall, great article!