Currying for JavaScript Developers with Examples

Currying for JavaScript Developers with Examples

What is currying? Practical examples of currying in JavaScript. Why currying is useful in programming. How to create curried functions

Fernando Doglio's photo
Fernando Doglio
·May 10, 2022·

9 min read

Subscribe to my newsletter and never miss my upcoming articles

Have you noticed how from all of the classic Array methods in JavaScript to iterate and perform some transformations over that data such as map and forEach the one that never really gets understood is reduce?

Well, the same happens with Currying in Functional Programming. Everyone is all in for higher-order functions, or even pure functions with no side effects, but the minute you start talking about currying, their faces go like this:

Source: giphy

And trust me, currying is nothing that deserves such a reaction. That said, instead of just throwing the definition at your face and the classic “hello world” example, I’ll show you a few different ways where you can make use of this technique, hopefully demonstrating that this is just another tool under your JS belt and that you should use it more often.

“Hello world”

We’ve got to start somewhere, right?

The most basic example of currying is great to understand what this technique is all about.

You see, currying is nothing more than a way to transform functions that accept multiple parameters into a sequential list of functions that take only one parameter each.

function sum(a, b) {
  return a + b;
}

const curriedSum = curry(sum)

console.log(curriedSum(1)(2)) //outputs 3

That’s the useless “hello world” example. The “magic” function curry is transforming our sum function into one that only takes one parameter and returns another function that again, takes one parameter and returns the sum of both numbers.

Is it difficult to implement? Heck no! In JavaScript this is relatively trivial:

const curry = fn => a => b => fn(a,b) 

//or using classical function notation

function curry2(fn) {
  return function(a) {
    return function(b) {
      return fn(a, b)
    }
  }
}

There you have 2 ways to implement it, although they’ll only work for functions with 2 arguments. We can later worry about making it more generic.

The point is that we turned a function into a list of partial functions. Take note of the term, “partial functions”, because we’ll be using it often.

Let’s take a look at a more relevant example: logging!.

Logging with a curried function

From now on, let’s assume we have a magical function curry that takes any function and properly curries it into having a list of functions, each one only accepting one parameter. I’ll show you how to implement it later, don’t worry.

Logging is something we do very often, in all of our applications we tend to have some kind of logging function that helps us debug problems. Sometimes it takes the form of console.log , others it’s Winston or a similar package. The point here is that we end up with a wrapper function that looks like this:

function log(level, date, message, extras) {
  //for the sake of simplicity, let's just use console.log
  console.log(`[{level}]::{date}::{message}`)
  if(extras) {
    console.log("Extra data: ", extras)
  }
}
`

Granted, you might have a variation of it or a wrapper on top of this one to simplify your logging syntax.

The aim of using currying here, is to give us some syntactic sugar when it comes to calling our logging function. And yes, syntactic sugar is actually a good thing, it can help make our code more readable and simpler to understand.

Let’s see, once we curry our log function, we get something like this:

const cLog = curry(log);

//We go from writing
log('DEBUG', Date(), "This is a debug message")

//To writing
cLog('DEBUG')(Date())("This is a debug message")

What’s the point of doing this? The point is that we can use partial functions to give us more readable and — very important point — reusable code. Imagine you have to write 100’s debug log lines throughout your code, would you rather use that syntax or this one:

const logDebug = cLog('DEBUG')(Date())

//... and then
logDebug("This is a debug message")

By using currying I pre-filled 2 of the 4 parameters that log receives. I’m not using the cLog function anywhere, but I can use logDebug everywhere and I can create similar versions of it with different pre-fills:

const infoMsg = cLog('INFO')(Date())
const errorMsg = cLog('ERROR')(Date())("There is an error here")
const infoMsgUnixTS = cLog('INFO')(Date.now()) //logs the unix timestamp

You see how we turned our original log function that could do everything but required 4 parameters, into multiple individual functions that do one thing but require fewer parameters. The point here is that it’s going to be a lot easier to write and much cleaner to read something like errorMsg(errorObj) than log("ERROR", Date(), "There is an error here", errorObj) , don’t you think?

Currying in this case provides several benefits:

  1. It helps us make our code more readable.
  2. It cleans up the code by abstracting some of the less important and repeatable values.
  3. It helps us re-use our code in multiple ways.

Filtering

Coming up with a generic filtering function for our data can be a great achievement. Finding a way to give yourself the power to enter a few parameters and then get the exact data you need is great. The problem is that filtering parameters can start to stack up, and then your filtering calls end up looking like this:

filter(data, 'first\_name', 'John', 'eq')

And yes, if you’re the one who implemented the function, you know what it means. If on the other hand, you’re just seeing it written there with no further context, you’ll have to guess what each attribute means, especially for that last one.

Look at the following example:

const data = [{
        first_name: "Fernando",
        last_name: "Doglio",
        age: 38,
        city: "Madrid"
    },
    {
        first_name: "John",
        last_name: "Doe",
        age: 40,
        city: "NY"
    },
    {
        first_name: "Jane",
        last_name: "Doe",
        age: 25,
        city: "NJ"
    },
    {
        first_name: "Optimus",
        last_name: "Prime",
        age: 100000,
        city: "Cybertron"
    }];


function filter(data, attribute, value, operator) {
    switch(operator) { 
        case 'gt':
            return data.filter( d => d[attribute] > value )
        break;
        case 'lt':
            return data.filter( d => d[attribute] < value )
        break;
        default:
        case 'eq':
            return data.filter( d => d[attribute] == value )
        break;
    }
}

The filter function is a crude wrapper around the filter method so that we don’t have to worry about creating that lambda function every time. Instead this abstracts us from it and we can just worry about our filtering parameters. Yes, there are better ways to do this, but this is not the point, the point is about to come.

Through currying, we can create partial versions of filter to capture common filtering options we might want to have. Things like filtering by age, or by name, so that we can simplify our syntax, as we did before with the logging example.

const cfilter = curry(filter);
const filterByAge = cfilter(data)('age')
const olderThan30 = filterByAge(30)('gt')
const olderThanTime = filterByAge(999)('gt')

const filterByName = cfilter(data)('first_name')
const me = filterByName('Fernando')()

console.log(olderThan30)
console.log("----")
console.log(olderThanTime)
console.log("----")
console.log(me)

At the start of the snippet above, we defined a set of pre-set filters. We can then see the results at the end: a much more readable scenario and a lot more flexible too.

The output from that example, just so we’re clear, is:

//olderThan30  
\[  
  {  
    first\_name: 'Fernando',  
    last\_name: 'Doglio',  
    age: 38,  
    city: 'Madrid'  
  },  
  { first\_name: 'John', last\_name: 'Doe', age: 40, city: 'NY' },  
  {  
    first\_name: 'Optimus',  
    last\_name: 'Prime',  
    age: 100000,  
    city: 'Cybertron'  
  }  
\]  
\----

//olderThanTime  
\[  
  {  
    first\_name: 'Optimus',  
    last\_name: 'Prime',  
    age: 100000,  
    city: 'Cybertron'  
  }  
\]  
\----

//me  
\[  
  {  
    first\_name: 'Fernando',  
    last\_name: 'Doglio',  
    age: 38,  
    city: 'Madrid'  
  }  
\]

As you can see, this is another example where currying helps to simplify the syntax of your logic, make it more readable, and provide you with the flexibility to re-use your code in many ways everywhere you need to.

Let’s now look at the best part: implementing the curry function.


If you liked what you’ve read so far, consider subscribing to my FREE newsletter “The rambling of an old developer” and get regular advice about the IT industry directly in your inbox


The Implementation

Let’s take a look at 2 versions of the curry function. The first one is going to be the one I’ve been using throughout the entire article, and then I’ll throw a curveball your way.

Like I’ve shown you, the curry function needs to return a sequence of callable functions that take one parameter each, based on the number of parameters for the original function. We can do this with a little recursion magic:

function curry(fn) {
    return function curried(...params) {

        //If we've collected all parameters
        if(params.length == fn.length ) {
            return fn.apply(this, [...params]) //we call the original function
        }
        return function(...params2) { //we return another partial function
            if(params2.length == 0) {
                params2.push(null) //for optional parameters
            }
            return curried.apply(this, params.concat(params2))
        }
   }
}

What the above code does is:

  1. It returns a new function called curried which is going to start capturing parameters inside the params array.
  2. If not called with the exact number of attributes the original function receives, it’ll assume it’s a partial call, thus it’ll return a new function.
  3. The new function receives another parameter, calls the original, and concatenates both lists of parameters together. Thus collecting all individual parameters.
  4. Now we go back to point 2, until of course, we’ve collected all required parameters, and then we call the original function using the apply method (which lets us pass all attributes as an array).

This works, you can test it with the examples given so far. But if we go back to the examples, especially the filtering ones, it feels like we’re not pushing our curry logic to the max.

It would be really cool if we could do something like this:

const cfilter = curry(filter);
const filterByAge = cfilter(data)('age')
const olderThan = filterByAge(_)('gt')

console.log(olderThan(30))
console.log(olderThan(20))

Essentially, it would be great to have some kind of wildcard value we could provide, so that it gets resolved during the execution of the filter. But our curry function doesn’t allow for it, not right now though.

We can allow for 1 wildcard to be used with the following logic:

const _ = "?"

function curry(fn) {
    return function curried(...params) {

        //If we've collected all parameters
        if(params.length == fn.length ) {

            let magicIndex = params.indexOf(_)
            if(magicIndex != -1) { //we check for wildcards
                return function(magic) {
                    params[magicIndex] = magic
                    return fn.apply(this, [...params])
                }
            }

            return fn.apply(this, [...params]) //we call the original function
        }
        return function(...params2) { //we return another partial function
            if(params2.length == 0) {
                params2.push(null) //for optional parameters
            }
            return curried.apply(this, params.concat(params2))
        }
   }
}

Now if a wildcard is used, when it’s time to call the original function, we return yet another function wrapping it. This new function will ask for the actual value of the wildcard before performing the final call.

That way, we gain even extra flexibility, and if you have the time and energy, you could also add support for a variable number of wildcards to be used across the currying chain. I’ll leave that to you!

Is currying a bit more understandable now? Does it make more sense?


Conclusion

Currying is definitely a powerful tool, one that can potentially take your code to the next level, but to do so, you have to fully understand how it works. Now that you do, you can probably use the currying methods from libraries such as lodash instead of building your own every time.

Do you still have questions? Leave them in the comments and I’ll do my best to help out!

Did you find this article valuable?

Support Fernando Doglio by becoming a sponsor. Any amount is appreciated!

See recent sponsors Learn more about Hashnode Sponsors
 
Share this