Currying with Javascript

Currying with Javascript

Currying is an important technique used both in mathematics and computer science. It is the technique of converting a function that takes multiple arguments into a sequence of functions that takes single arguments each.

While it may sound like the name is inspired by something food, as it can be easily related to culinary techniques, it was actually named after Haskell Curry who polished the concept after it was introduced by Gottlob Frege and developed by Moses Schönfinkel.

Currying is not exclusive to JS, as mentioned it is an important concept in computer science, and so it can be implemented using any language support First Class Functions, ie. functions can we assigned to variables, passed as arguments to other functions and returned from functions.

A simple example of currying would be : Basic currying add.png

So like we discussed, by currying the function we broke a function that takes multiple arguments into a sequence of functions that takes single arguments each.

add(1, 2)

can now be called as

add(1)(2)

Now before we continue with different examples of currying and how to "curry a given function", it helps a lot if we can understand why we need this or where it is actually used.

Let us suppose we have a logging function like:

function log(date, importance, message) {
  alert(`[${date.getHours()}:${date.getMinutes()}] [${importance}] ${message}`);
}

And we usually call it by passing all the arguments needed like :

log(new Date(), "DEBUG", "some debug");  // ie. like - log(a, b, c)

But once curried, we can call it like :

log(new Date())("DEBUG")("some debug"); // ie. like - log(a)(b)(c)

Now, with currying, we can actually make our life a lot easier.

Suppose we want a function that prints the log right now. Instead of giving the date argument each time, we can do this :

logNow.png Another example would be to further extend this to only log the info right now, by which we can pre-set the 2nd argument as well and create a function :

infoNow.png

So by currying, we still have our original log function working normally, and we have made some new partial functions that can do specific things with fewer arguments and ease.

Now that we understood an application, let's talk about how to curry.

For interviews and exams, we usually see questions that ask us to make an add function with multiple varying numbers of arguments. So let's use those to see different ways of making curried functions.

If we want to create a function that will add numbers together when called in succession.

add(1)(2); // returns 3
add(1)(2)(3); // 6
add(1)(2)(3)(4); // 10
add(1)(2)(3)(4)(5); // 15

We can use the valueOf property to implement that:

sum currying.png For example, if we have this (I have to thank Phil DeJarnett( OverZealous from codewars) for this wonderful explanation) :

let obj = { x: 1 };
console.log(obj + 1);

What the JS engine does in the background will be to convert obj into the string "[object Object]", and then try to add 1 to it, creating in the string "[object Object]1".

Now, if we add a method valueOf into the object like:

let obj = {
  x: 1,
  valueOf: function () {
    return this.x;
  },
};
console.log( obj + 1 );

The JS engine will see that valueOf exists on the object, and call that. In this case, we will return the value of x on the object, and the output becomes 2.

The other part is how we are using this on the returned method here. So, Functions in JavaScript are First-class citizens like we discussed, it is made possible because they are also objects and can have properties and methods added to them, just like normal objects.

What happens here is we return a new function that can be used to add the previous value (sum) and the new value (y). We also add a valueOf method to the new function, which returns the sum.

So, if we have code that looks like this:

let n1 = add(1); // n1 is a Function, but also has a valueOf property
let n2 = n1(2);  // same for n2

We can coerce the n1 and n2 functions into values by using them in an arithmetic or logical expression, like so:

console.log( 0 + n1 ) // => 0 + n1.valueOf() => 0 + 1 => 1
console.log( 0 + n2 ) // => 0 + n2.valueOf() => 0 + (1+2) => 3

But will fail if we try

add(3)(4)(5) // return function
console.log(add(3)(4)(5)) // output: function
console.log(add(3)(4)(5) === 12) // output: false

Another option like we told above is to return the sum when the function is called with no arguments. If argument is given, it will keep on adding those numbers to the result.

sum currying 2.png This can be used like:

add(2)(3)() // output: 5
let sum = add(3)(4)(5)
sum() // output: 12

But this has the limitation as it needs to end with empty arguments () to get the resulting value, else it just returns a function.

Currying and partial function application are often conflated. One of the significant differences between them is that a call to a partially applied function returns the result right away, not another function down the currying chain; this distinction can be illustrated clearly for functions whose arity is greater than two.

Now that we have discussed how to create curried add functions, let us see how we can try curry any function, where we create a wrapper function curry, which takes a function as its input and returns it curried.

currying Function.png The explanation is commented in the code as you can see. Now, if we call it, again, we will get either a new partial (if not enough arguments) or, finally, the result.

The limitation of currying is that it requires the function to have a fixed number of arguments. Functions that use rest parameters, like add(...args), can’t be curried this way. While currying should convert add(a, b, c) into add(a)(b)(c), in JavaScript the implementations are advanced, as described: they also keep the function callable in the multi-argument variant, add(a,b,c).

So to summarise , Currying is a transform that makes func(a,b,c) callable as func(a)(b)(c). JavaScript implementations usually both keep the function callable normally and return the partial if the arguments count is not enough.

Please feel free to comment on any suggestion or criticism you have. Always looking to learn and improve.