loading...
Cover image for What the heck is Currying anyway?

What the heck is Currying anyway?

heytulsiprasad profile image Tulsi Prasad Updated on ・6 min read

Hey everyone, nice to see you on my little home on internet! I'm going through some concepts which I had skipped while beginning to learn JavaScript and here we are. So, now we'll see what's the matter with one of the fancy terms in JavaScript, Currying.

Literally,

Despite being a fancy term, (according to Wikipedia) currying is a technique of converting a function that takes multiple arguments into a sequence of functions that each take a single argument. Now whether you're a JavaScript fanboy or did basic algebra in high school that'll totally make sense to you.

But if not, all it says is if we have a function that takes in more than one arguments and we are converting it into another function that takes those number of arguments but one at a time or sequentially. The below picture makes it clear.

Anyways, how does that happen in real world, (or programatically) let's find out!

Example of Currying

How to write a curry function?

We know how to write a normal function (without currying), for example add() above, which is simple as such.

function add(a, b, c) {
  return a + b + c;
}

console.log(add(1, 2, 3)); // 6

But this is how we write the same function in a curriable manner.

function add(a) {
  return function (b) {
    return function (c) {
      return a + b + c;
    };
  };
}

console.log(add(1)(2)(3)); // 6

If you notice in the curry function, for every passed argument inside add() we are returning one new function which takes another argument and returns another function. And at last, after passing the last argument we return the final result. This is the basic struture of a curry function.

So for add(1) in the curry function, we don't get a value as a result rather a whole new function which takes (2) as an argument and it goes on until we get a value in output.

Using with helper functions

As currying function is not easier to write (always), but being a major concept in functional programming we have many helper functions that help us transform a normal function into a curry function.

These can be done by most JavaScript utility libraries like Lodash, Rambda and so on, with one simple step like this. Also, I'm using lodash in this example.

const _ = require("lodash");

function add(a, b, c) {
  return a + b + c;
}

const curryAdd = _.curry(add);

console.log(add(1, 2, 3)); // 6
console.log(curryAdd(1)(2)(3)); // 6

Creating new functions from existing ones

This is how curry functions are mostly used in real life as it helps us create entire new functions and export them to use it anywhere .

For instance, we are having this array of objects.

const items = [
  { name: "Mango", type: "Fruit" },
  { name: "Tomato", type: "Vegetable" },
  { name: "Strawberry", type: "Fruit" },
  { name: "Potato", type: "Vegetable" },
  { name: "Turnip", type: "Vegetable" },
  { name: "Banana", type: "Fruit" },
  { name: "Carrot", type: "Vegetable" },
];

Now we need to create a function, that returns all items of type Fruits or Vegetables. So, let's do this using the curry concept that we just learned.

const isType = obj => type => obj.type === type;

const isFruit = item => isType(item)("Fruit");
const isVegetable = item => isType(item)("Vegetable");

const fruits = items.filter(isFruit);
const vegetables = items.filter(isVegetable);

Woah, this looks so clean. But how does this work?

Firstly we are having the isType() as a curry function, which takes in an object and returns a boolean (true/false) after checking it's type is equal to the passed argument type or not.

But instead of using this function directly, we are creating two more functions that checks for whether type is fruit or vegetable separately. By seeing it's structure, you'll notice it just takes one argument which is the current item and calls isType() by passing item and the desired type.

Finally, to get our fruits and vegetables we run a .filter() array method passing either isFruit or isVegetable as a callback function. This by default passes our current item inside items array to the callback. To be more clear, what we've done in the end is same as, below.

const fruits = items.filter(item => isFruit(item));
const vegetables = items.filter(item => isVegetable(item));

Results

console.log(fruits)

[
  { name: 'Mango', type: 'Fruit' },
  { name: 'Strawberry', type: 'Fruit' },
  { name: 'Banana', type: 'Fruit' }
]

console.log(vegetables)

[
  { name: 'Tomato', type: 'Vegetable' },
  { name: 'Potato', type: 'Vegetable' },
  { name: 'Turnip', type: 'Vegetable' },
  { name: 'Carrot', type: 'Vegetable' }
]

Ultimately, we came to know how to write a curry function, which was way easier than we thought.

Edit

Can you think of a slightly different way to write our functions in the above example, to write less code and make it more straightforward? Is it possible?

Yes and thanks to Rudy NappΓ©e for showing us the same in the comments and I thought it'll be useful for everyone reading this post.

So, what we should do is: "Always put the point (argument on which the partial application operates in the end) at the last position".

const isType = (type) => (obj) => obj.type === type

In this way, we don't have to redefine yet another function, like we did for isFruit and isVegetable , instead we can write.

const isFruit = isType("Fruit")
const isVegetable = isType("Vegetable")

Or you can write it in a more straightforward way, directly in the filter callbacks.

const fruits = items.filter(isType("Fruit"))
const vegetables = items.filter(isType("Vegetable"))

Making our own helper function

After being able to transform a normal function to a curry function using a helper function, like .curry() from Lodash, I was curious to know how to build one for ourselves. Because, you know at times, we only need handful of functions for which including a giant utility library like Lodash isn't necessary.

Let's create a simple usecase first, then we'll go off to create the actual curry function.

// Assume that curry is a valid function

const multiply = (a, b) => a * b;

const curryMultiply = curry(multiply);

console.log(curryMultiply(2, 4)); // 8
console.log(curryMultiply(2)(4)); // 8

Don't hit run now, as we yet have to fulfill our assumption and create a valid curry function. And upon running this will definitely give a Reference Error as curry is not defined.

Building the actual curry() function.

function curry(func) {
  return function curried(...args) {
    if (args.length >= func.length) {
      return func.apply(undefined, args);
    } else {
      return function (...rest) {
        return curried.apply(undefined, rest.concat(args));
      };
    }
  };
}

Firstly, we are creating a function named curry and return another function from it, which is named as curried. As you can see, in the returned function we are checking the number of arguments passed to it, if the number exceeds or is equal to number of arguments the actual function func expects (also called arity) then we return the function by calling apply and passing in all the args.

In case, we got less number of arguments (which happens while we pass parameters one by one) we return another function which stores its arguments in a variable called rest. Finally, we call the same function curried() recursively and pass it the new arguments in rest variable and concat it with previosly gotten arguments in args variable.

Also, if you're wondering what those 3 dots are (...), they're a new feature in ES6 or ECMAScript2015. They return us all the passed arguments to a function.

Now, if you press enter or run your code you'll see you get the right output, like this.

console.log(curryMultiply(2, 4)); // 8
console.log(curryMultiply(2)(4)); // 8

You can save this snippet in your Gist or understand it properly, so anytime you just want to use a function with currying instead of writing the function again, you can create a new function altogether by passing old non-curriable function into this curry() method we just created.

Hurray πŸŽ‰

If you're with me yet, then hurray! I hope you learned something new or you could get an edge over fixing hour old bugs. Throw me a tweet at @heytulsiprasad if you find something I missed or just want to say Hi (really that's becoming important these days). You can expect more blogs on functional programming in coming days.

Further Reading

Posted on by:

heytulsiprasad profile

Tulsi Prasad

@heytulsiprasad

Aspiring frontend engineer. Love sharing what I learn and read. Writing articles on wirescript.now.sh and opinions on Twitter. Actively making new friends on internet!

Discussion

pic
Editor guide
 

Good summary on the fundamentals of currying, Tulsi.

I use curried funcs (funcs that return funcs) every day.
However I do not use them for reuse sakes. i.e. to define new functions (that's too much like inheritance).

I will define a func that has two or more parameters β€” and who's purpose is to be used in a pipe β€” as a curried func (nested func).
This allows one to delay final execution until the final value for the pipe is satisfied.
So a curried func is simply a closure.

So I think in terms of making pipeable funcs, which are a more generalized design pattern than currying since it applies also to uncurried funcs that take zero or one argument.

Btw, here is an example screenshot of method chaining verses functional pipelining.
Note that the funcs in the functional pipeline may or may not be curried, but they are all pipeable funcs.
functional pipeline example

 

Yeah, thanks for sharing a real world insight into using currying.

I've used method chaining for uncountable times however I am new to the term piping, but do understand the concept of it though. If possible, can you show a small example of how to make piping functions and how they're useful?

 

Hey Tulsi,

It turned out to be a big example :), so I posted it here...
dev.to/functional_js/a-functional-...

 

Great explanation on a not so easy concept.

However you should always put the "point" (argument on which the partial application operates in the end) in the last position :

const isType = type => obj => obj.type === type;

This way you don't need to redefine functions, just :

const isFruit = isType("Fruit");
const isVegetable = isType("Vegetable");

Or write in a straightforward way :

const fruits = items.filter(isType('fruit'));
const vegetables = items.filter(isType('Vegetable'));
 

Thanks for the great tip! I'll add this to the blog soon! πŸ€—

 

Finally got some time to update the post with this. Thanks again, for sharing!

 

Cool post! The motivation behind currying came from lambda calculus. In lambda calculus, you can only have anonymous unary functions. That means your function can only take one parameter and there's no way of calling other functions because they're anonymous, so the only way to do it is by nesting your function calls, a.k.a currying.

Here's the identity function. It takes 1 argument and returns it.

Ξ»x.x

In pseudo javascript this looks like

(x) => x

What if we want the Kestrel/K combinator? In javascript, it can be expressed like this

(x, y) => x

But how would we do that in lambda calculus? By calling another lambda expression.

Ξ»x.(Ξ»y.x)

In javascript, this will look like

(x) => (y) => x

So, in javascript it looks like you can just have more arguments in your function. Why would you want to do currying if it's capable of taking in more than one argument? Well.. the cool thing about curried functions combined with naming them, is you can partially apply them.

const addNum = x => y => x + y
const addOne = addNum(1)
// y => 1 + y
const myNum = addOne(10)
// 11
const myNiceNum = addOne(69)
// 70

And because you're using unary functions it's easier to identify the cardinality of each function.

EDIT: Using the above example with a compose function... coz partially applied functions with composition is pretty cool

const compose = (...fns) => (arg) =>
  fns.reduceRight((acc, fn) => (fn ? fn(acc) : acc), arg)

const myNum = compose(addOne, addOne, addOne, addOne)(1)
// 5
 

Thanks for sharing the backstory behind currying! Now, I'll definitely keep an eye out when Lambda calculus comes up in my college. I'll have something to relate to! πŸ’™

 

I'll echo that as I was struggling to understand why you would bother. Of course, lambda (something I don't yet use a lot) makes perfect sense.

 

Woah, this looks so clean.
From the code execution and efficiency point of view it's a nightmare. Instead of using full potential of your CPU you're making it sub-optimal the least by abusing function calls.
In normal situation you'd store the parameters in available registers or push them to the heap, store the stack pointer, redirect the code by updating the instruction pointer, use generic add instruction multiple times and then call return and remove the address from stack.
With curry function you're doing this multiple times increasing nesting, filling the stack (I don't want to debug this by looking at the stack trace, must look like a mess), making the code more vulnerable to thread unsafety as there is much more room when the thread can be pre-empted.
I can't see a reason why would anyone use this in production code. I assume any good compiler would inline those function calls anyways leaving you with significantly different debug and production code which might result in weird bugs that you cannot reproduce.

 

Normal function in IL:

IL_0000: ldarg.0      // a
IL_0001: ldarg.1      // b
IL_0002: add
IL_0003: ldarg.2      // c
IL_0004: add
IL_0005: ldarg.3      // d
IL_0006: add
IL_0007: ret

// function call
IL_0000: ldc.i4.1
IL_0001: ldc.i4.2
IL_0002: ldc.i4.3
IL_0003: ldc.i4.4
IL_0004: call  int32 CurryDemo.BenchmarkNormalVsCurry::NormalAdd(int32, int32, int32, int32)

Curry function in IL:

IL_0000: newobj  instance void CurryDemo.BenchmarkNormalVsCurry/'<>c__DisplayClass1_0'::.ctor()
IL_0005: dup
IL_0006: ldarg.0      // a
IL_0007: stfld  int32 CurryDemo.BenchmarkNormalVsCurry/'<>c__DisplayClass1_0'::a

IL_000c: ldftn  instance class [System.Runtime]System.Func`2<int32, class [System.Runtime]System.Func`2<int32, int32>> CurryDemo.BenchmarkNormalVsCurry/'<>c__DisplayClass1_0'::'<CurryAdd>b__0'(int32)
IL_0012: newobj  instance void class [System.Runtime]System.Func`2<int32, class [System.Runtime]System.Func`2<int32, class [System.Runtime]System.Func`2<int32, int32>>>::.ctor(object, native int)
IL_0017: ret

// function call
IL_0000: ldc.i4.1
IL_0001: call         class [System.Runtime]System.Func`2<int32, class [System.Runtime]System.Func`2<int32, class [System.Runtime]System.Func`2<int32, int32>>> CurryDemo.BenchmarkNormalVsCurry::CurryAdd(int32)
IL_0006: ldc.i4.2
IL_0007: callvirt     instance !1/*class [System.Runtime]System.Func`2<int32, class [System.Runtime]System.Func`2<int32, int32>>*/ class [System.Runtime]System.Func`2<int32, class [System.Runtime]System.Func`2<int32, class [System.Runtime]System.Func`2<int32, int32>>>::Invoke(!0/*int32*/)
IL_000c: ldc.i4.3
IL_000d: callvirt     instance !1/*class [System.Runtime]System.Func`2<int32, int32>*/ class [System.Runtime]System.Func`2<int32, class [System.Runtime]System.Func`2<int32, int32>>::Invoke(!0/*int32*/)
IL_0012: ldc.i4.4
IL_0013: callvirt     instance !1/*int32*/ class [System.Runtime]System.Func`2<int32, int32>::Invoke(!0/*int32*/)

Benchmark:

// * Summary *

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.450 (2004/?/20H1)
AMD Ryzen 7 3800X, 1 CPU, 16 logical and 8 physical cores
.NET Core SDK=3.1.401
  [Host]        : .NET Core 3.1.7 (CoreCLR 4.700.20.36602, CoreFX 4.700.20.37001), X64 RyuJIT
  .NET Core 3.1 : .NET Core 3.1.7 (CoreCLR 4.700.20.36602, CoreFX 4.700.20.37001), X64 RyuJIT

Job=.NET Core 3.1  Runtime=.NET Core 3.1

|        Method |       Mean |     Error |    StdDev |
|-------------- |-----------:|----------:|----------:|
| TestNormalAdd |  0.0203 ns | 0.0093 ns | 0.0083 ns |
|  TestCurryAdd | 33.1419 ns | 0.2941 ns | 0.2607 ns |

// * Hints *
Outliers
  BenchmarkNormalVsCurry.TestNormalAdd: .NET Core 3.1 -> 1 outlier  was  removed (1.57 ns)
  BenchmarkNormalVsCurry.TestCurryAdd: .NET Core 3.1  -> 1 outlier  was  removed, 2 outliers were detected (33.77 ns, 35.37 ns)

// * Legends *
  Mean   : Arithmetic mean of all measurements
  Error  : Half of 99.9% confidence interval
  StdDev : Standard deviation of all measurements
  1 ns   : 1 Nanosecond (0.000000001 sec)

// ***** BenchmarkRunner: End *****

the program:

    class Program
    {
        static void Main(string[] args)
        {
            var summary = BenchmarkRunner.Run<BenchmarkNormalVsCurry>();
        }
    }

    [SimpleJob(RuntimeMoniker.NetCoreApp31)]
    [RPlotExporter]
    public class BenchmarkNormalVsCurry
    {
        public static int NormalAdd(int a, int b, int c, int d)
        {
            return a + b + c + d;
        }

        public static Func<int, Func<int, Func<int, int>>> CurryAdd(int a) =>
            (b) =>
                (c) =>
                    (d) => a + b + c + d;

        [Benchmark]
        public void TestNormalAdd() => NormalAdd(1, 2, 3, 4);

        [Benchmark]
        public void TestCurryAdd() => CurryAdd(1)(2)(3)(4);
    }
 

Fantastic that you emphasize this. While FP code constructs unclutter the code, without proper tool support you just move the clutter into the runtime.

Indeed, JavaScript (nor C#) was never designed to go full throttle on functional programming concepts. Therefor the JIT compiler doesn't optimize any of it.

Languages like Haskell, F#, Erlang, Lisp, are built for this, and will handle it in a better way.

 

Thanks man, for putting this out here!

I'm definitely interested to know what happens behind all the functions and about compilation. However, I'm not quite experienced at it, but hopefully this will be helpful to others and even me in future.

 

Am I misunderstanding, or are the 2nd console.log() calls in your curryMultiply examples incorrect? Shouldn't they be calling curryMultiply() instead of multiply()?

 

You're very right, it was a mistake from my side of not cross checking the code on an editor. Don't know how I missed that.

Thanks a lot, I've updated it now. πŸ˜€

 

Great. However, were you supposed to update the first console.log()? Unless I misunderstand, the first one should just call multiply().

I did it to show that we can also use curryMultiply in the way we normally should have used multiply function. Also, I felt it was kinda obvious that multiply(2, 4) will always return 8 as its just a normal function.

You can call curryMultiply() with two parameters and it will pass them all? I thought it only worked with one parameter (I have a lot more experience with PHP and Go than with Javascript, so I do not know how JS works well enough.)

If it can be used that way then can you please explain that in comments in the code snippet or elsewhere in the article to clarify for others who assume the same as me because that was not at all clear to me when reading?

Oops I got caught up in college stuff, apologies!

For now you can fiddle around with this repl to understand how it works! If possible, I'll add a line about this on the blog tomorrow. 🀞 Link to repl

The actual thing is as these two lines of code exist in our custom curry function:

  if (args.length >= func.length) {
      return func.apply(undefined, args);

We check if we already have got enough arguments that are necessary for the function to execute (or arity of that function), as in this case curryMultiply(2, 4). If this condition holds true, we apply (or invoke) the function right away instead of awaiting for another argument like we do for cases like curryMultiply(2)(4)

which is when the else block of the code executes

} else {
      return function (...rest) {
        return curried.apply(undefined, rest.concat(args));
      };

Thanks for the detailed reply and REPL.

P.S. RE: "College stuff"; I don't envy you! I did my time years ago and thinking about it still gives me PTSD! :-D

 

Nice, didn't know that that technique was called currying. πŸ˜„

 

Yeah me too, I remember using them with Thunk and Connect functions with Redux, but never thought there'd be a name for this. 😜

 

Is either this or this that's the curry

 

I only had this curry in mind though! 😜