函数式编程中的柯里化到底有什么用?

函数式编程最近越来越活跃,去年实习的时候买了一本 《JavaScript 函数式编程》,囫囵吞枣的看了一遍,似懂非懂的, 今年重新看了一遍,现写下这篇博客,谈谈我对柯里化的理解吧。

柯里化

柯里化函数为每一个逻辑参数返回一个新函数。(《JavaScript 函数式编程》)

简单说,函数柯里化就是对高阶函数的降阶处理。
举个例子,就是把原本:
function(arg1,arg2)变成function(arg1)(arg2)
function(arg1,arg2,arg3)变成function(arg1)(arg2)(arg3)
function(arg1,arg2,arg3,arg4)变成function(arg1)(arg2)(arg3)(arg4)
……
function(arg1,arg2,…,argn)变成function(arg1)(arg2)…(argn)

作者:小蝶惊鸿
链接:https://www.zhihu.com/question/40374792/answer/86268208
来源:知乎
著作权归作者所有,转载请联系作者获得授权。

举个例子

一个参数

强制只接收一个参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 接收一个参数自动柯里化
function curry (fun) {
return function (arg) {
return fun(arg);
}
}

// es6 装逼版
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]

两个参数

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
// 普通二参数的加法
function normalAdd(x, y) {
return x + y;
}

// 柯里化版本
function add(y) {
return function(x) {
return x + y;
}
}

let add2 = add(2);

add2(3);
// 5

// 普通二参数乘法
function normalMultiply(x, y) {
return x * y;
}

// 柯里化版本
function multiply(y) {
return function(x) {
return x * y;
}
}

let multiply2 = multiply(2);

multiply2(3);
// 6

// 自动柯里化
function curry2 (fun) {
return function (arg2) {
return function (arg1) {
return fun(arg1, arg2);
}
}
}

// es6 装逼版
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

三个参数

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
// 普通版本
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]


// 柯里化版本
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]


// 自动柯里化
function curry3 (fun) {
return function (last) {
return function (middle) {
return function (first) {
return fun(first, middle, last);
}
}
}
}

// es6 装逼版
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]

柯里化到底有什么用

每个步骤都是显性调用(消耗一个参数),同时将该步骤的结果缓存(返回匿名闭包,该闭包等待下一个参数),从而暂缓调用,待时机成熟时便可传入下一个参数以便继续调用;

两个参数情况下的应用

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
  // 用于定义一系列 action
actionList = [{
"action": "isLogin",
"hasCallback": true
}, {
"action": "doLogin",
"hasCallback": false
}, {
"action": "setTitle",
"hasCallback": true
}];

// 批量生成 API 的工厂函数
factory(actionList) {
for (let value of actionList) {
this[`${value.action}`] = this.generator(value);
}
}

// 简化版本的 API 生成函数
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);
});
}
}

// 最终的调用方式, 其中 params 是用户调用时才传入的
bridge.setTitle({skin: 'red', color: '#666'})
.then((data) => {
alert(data);
})
.catch((err) => {
alert(err);
});

三个参数情况下的应用

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

该中间件期待一个第一个参数 { dispatch, getState }, 并返回一个期待一个 next 参数的匿名函数, 由于 next 的值由上一个中间件决定, 因此暂缓调用, 直至传入 next 参数, 最终返回一个新的函数(即加入中间件的 dispatch 函数), 该函数期待一个 action 参数。

具体调用过程及原理详见:理解 redux 中间件

参考:
专属前端坑的函数式编程
深入到源码:解读 redux 的设计思路与用法
理解 redux 中间件

作者

林宜丙

发布于

2016-10-06

更新于

2023-08-30

许可协议