What Is Currying Actually Useful For in Functional Programming?

Functional programming has been increasingly active lately. When I was interning last year, I bought a copy of “Functional Programming in JavaScript,” read through it hastily with only a vague understanding. After re-reading it this year, I’m writing this blog post to share my understanding of currying.

Currying

A curried function returns a new function for each logical argument. (Functional Programming in JavaScript)

Simply put, currying is the process of reducing the arity of higher-order functions.
For example, transforming:
function(arg1,arg2) into function(arg1)(arg2)
function(arg1,arg2,arg3) into function(arg1)(arg2)(arg3)
function(arg1,arg2,arg3,arg4) into function(arg1)(arg2)(arg3)(arg4)

function(arg1,arg2,…,argn) into function(arg1)(arg2)…(argn)

Author: Xiaodie Jinghong
Link: https://www.zhihu.com/question/40374792/answer/86268208
Source: Zhihu

Examples

One Argument

Force accepting only one argument

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Auto-curry for single argument
function curry (fun) {
return function (arg) {
return fun(arg);
}
}

// ES6 version
function curry (fun) {
return arg => fun(arg);
}

[1, 2, 3, 4, 5].map(parseInt)
//[1, NaN, NaN, NaN, NaN]

[1, 2, 3, 4, 5].map(curry(parseInt))
//[1, 2, 3, 4, 5]

Two Arguments

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// Normal two-argument addition
function normalAdd(x, y) {
return x + y;
}

// Curried version
function add(y) {
return function(x) {
return x + y;
}
}

let add2 = add(2);

add2(3);
// 5

// Normal two-argument multiplication
function normalMultiply(x, y) {
return x * y;
}

// Curried version
function multiply(y) {
return function(x) {
return x * y;
}
}

let multiply2 = multiply(2);

multiply2(3);
// 6

// Auto-curry
function curry2 (fun) {
return function (arg2) {
return function (arg1) {
return fun(arg1, arg2);
}
}
}

// ES6 version
function curry2 (fun) {
return arg2 => arg1 => fun(arg1, arg2);
}

let curryAdd = curry2(normalAdd);
let curryAdd2 = curryAdd(2);

let curryMultiply = curry2(normalMultiply);
let curryMultiply2 = curryMultiply(2);

curryAdd2(3);
// 5

curryMultiply2(3);
// 6

Three Arguments

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// Normal version
function normalAddThenMultiply(arr, factor, increase) {
let tempArr = arr.map(function(ele, index) {
return normalAdd(ele, increase);
});

return tempArr.map(function(ele, index) {
return normalMultiply(ele, factor);
});
}

normalAddThenMultiply([1, 2, 3], 3, 2);
// [9, 12, 15]


// Curried version
function addThenMultiply(increase){
return function(factor) {
return function(arr) {
let addStep = curry2(normalAdd);
let multiplyFactor = curry2(normalMultiply);
let tempArr = arr.map(addStep(increase));
return tempArr.map(multiplyFactor(factor));
}
}
}

let add2Multiply = addThenMultiply(2);

let add2Multiply3 = add2Multiply(3);

add2Multiply3([1, 2, 3]);
// [9, 12, 15]


// Auto-curry
function curry3 (fun) {
return function (last) {
return function (middle) {
return function (first) {
return fun(first, middle, last);
}
}
}
}

// ES6 version
function curry3(fun) {
return last => middle => first => fun(first, middle, last);
}

let curryAddMultiply = curry3(normalAddThenMultiply);
let curryAdd2Multiply = curryAddMultiply(2);
let curryAdd2Multiply3 = curryAdd2Multiply(3);

curryAdd2Multiply3([1, 2, 3]);
// [9, 12, 15]

What Is Currying Actually Useful For

Each step is an explicit call (consuming one argument), while caching the result of that step (returning an anonymous closure that awaits the next argument), thereby deferring the call. When the time is right, the next argument can be passed to continue the invocation.

Application with Two Arguments

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
  // Used to define a series of actions
actionList = [{
"action": "isLogin",
"hasCallback": true
}, {
"action": "doLogin",
"hasCallback": false
}, {
"action": "setTitle",
"hasCallback": true
}];

// Factory function for batch-generating APIs
factory(actionList) {
for (let value of actionList) {
this[`${value.action}`] = this.generator(value);
}
}

// Simplified API generator function
generator(action) {
return function(params) {

let MyPromise = es6Promise.Promise;

action['params'] = params;

return new MyPromise((resolve, reject) => {
let callbackId = this.generateId();
this.responseCallbackList[callbackId] = (data) => {
resolve(data);
}
this.sendAction(action, callbackId);
});
}
}

// Final usage, where params are passed by the user at call time
bridge.setTitle({skin: 'red', color: '#666'})
.then((data) => {
alert(data);
})
.catch((err) => {
alert(err);
});

Application with Three Arguments

1
2
3
4
5
// redux-thunk middleware
export default function thunkMiddleware({ dispatch, getState }) {
return next => action =>
typeof action === 'function' ? action(dispatch, getState) : next(action);
}

This middleware expects a first argument { dispatch, getState } and returns an anonymous function expecting a next parameter. Since the value of next is determined by the previous middleware, the call is deferred until the next parameter is passed. Finally, it returns a new function (the enhanced dispatch function with middleware), which expects an action parameter.

For detailed invocation process and principles, see: Understanding Redux Middleware

References:
Functional Programming for Frontend Developers
Deep Dive into Source Code: Understanding Redux Design and Usage
Understanding Redux Middleware

What Is Currying Actually Useful For in Functional Programming?

http://quanru.github.io/2016/10/06/What-Is-Currying-Actually-Useful-For

Author

LinYiBing

Posted on

2016-10-06

Updated on

2026-03-15

Licensed under