# 柯里化
# 1. 简介
- 柯里化(Currying),又称部分求值(Partial Evaluation),是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
- 核心思想是把多参数传入的函数拆成单参数(或部分)函数,内部再返回调用下一个单参数(或部分)函数,依次处理剩余的参数。 按照Stoyan Stefanov --《JavaScript Pattern》作者 的说法,所谓“柯里化”就是使函数理解并处理部分应用
柯里化有3个常见作用:
- 参数复用
- 提前返回
- 延迟计算/运行
# 基本实现
function currying(fn, ...rest1) {
return function(...rest2) {
return fn.apply(null, rest1.concat(rest2))
}
}
测试
function sayHello(name, age, fruit) {
console.log(console.log(`我叫 ${name},我 ${age} 岁了, 我喜欢吃 ${fruit}`))
}
const curryingShowMsg1 = currying(sayHello, '小明')
curryingShowMsg1(22, '苹果') // 我叫 小明,我 22 岁了, 我喜欢吃 苹果
const curryingShowMsg2 = currying(sayHello, '小衰', 20)
curryingShowMsg2('西瓜') // 我叫 小衰,我 20 岁了, 我喜欢吃 西瓜
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 常见用法
- 参数复用
通过柯里化方法,缓存参数到闭包内部参数,然后在函数内部将缓存的参数与传入的参数组合后apply/bind/call给函数执行,来实现参数的复用,降低适用范围,提高适用性。
var currying = function(fn) {
var args = [].slice.call(arguments, 1);
return function(...rest) {
var newArgs = args.concat(...rest);
return fn.apply(null, newArgs);
}
}
var getUserInfo = currying(function() {
console.log([...arguments].join(';')) // allwife 就是所有的老婆的,包括暗渡陈仓进来的老婆
}, '面试人:')
getUserInfo('小明', '20岁', '大学本科') // 面试人:;小明;20岁;大学本科
getUserInfo('小刚、21岁、研究生') // 面试人:;小刚、21岁、研究生
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
- 提高适用性
通用函数解决了兼容性问题,但同时也会再来,使用的不便利性,不同的应用场景往,要传递很多参数,以达到解决特定问题的目的。有时候应用中,同一种规则可能会反复使用,这就可能会造成代码的重复性。
// 未柯里化前
function square(i) { return i * i; }
function dubble(i) { return i * 2; }
function map(handler, list) { return list.map(handler); }
map(square, [1, 2, 3, 4, 5]); // 数组的每一项平方
map(square, [6, 7, 8, 9, 10]);
map(dubble, [1, 2, 3, 4, 5]); // 数组的每一项加倍
map(dubble, [6, 7, 8, 9, 10]);
// 柯里化后
function currying(fn, ...rest1) {
return function(...rest2) {
return fn.apply(null, rest1.concat(rest2))
}
}
function square(i) { return i * i; }
function dubble(i) { return i * 2; }
function map(handler,list) {
return list.map(handler);
}
var mapSQ = currying(map, square);
mapSQ([1, 2, 3, 4, 5]);
mapSQ([6, 7, 8, 9, 10]);
var mapDB = currying(map, dubble);
mapDB([1, 2, 3, 4, 5]);
mapDB([6, 7, 8, 9, 10]);
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
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
可以看到这里柯里化方法的使用和偏函数比较类似,顺便回顾一下偏函数~
偏函数
是创建一个调用另外一个部分(参数或变量已预制的函数)的函数,函数可以根据传入的参数来生成一个真正执行的函数。比如:
const isType = function(type) {
return function(obj) {
return Object.prototype.toString.call(obj) === `[object ${type}]`
}
}
const isString = isType('String')
const isFunction = isType('Function')
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
- 延迟执行 柯里化的另一个应用场景是延迟执行。不断的柯里化,累积传入的参数,最后执行。例如累加
const curryAdd = function(...rest) {
const _args = rest
return function cb(...rest) {
if (rest.length === 0) {
return _args.reduce((sum, single) => sum += single)
} else {
_args.push(...rest)
return cb
}
}
}() // 为了保存添加的数,这里要返回一个闭包
curryAdd(1)
curryAdd(2)
curryAdd(3)
curryAdd(4)
curryAdd() // 最后计算输出:10
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
更通用的写法,将处理函数提取出来:
const curry = function(fn) {
const _args = []
return function cb(...rest) {
if (rest.length === 0) {
return fn.apply(this, _args)
}
_args.push(...rest)
return cb
}
}
const curryAdd = curry((...T) =>
T.reduce((sum, single) => sum += single)
)
curryAdd(1)
curryAdd(2)
curryAdd(3)
curryAdd(4)
curryAdd() // 最后计算输出:10
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
经典面试
如何实现下面输出结果都一样 fn(1, 2, 3, 4, 5) fn(1, 2)(3, 4, 5) fn(1, 2)(3)(4)(5) fn(1)(2)(3)(4)(5)
js
复制代码
function curry(fn, len = fn.length) {
return _curry(fn, len)
}
function _curry(fn, len, ...arg) {
return function (...params) {
let _arg = [...arg, ...params]
if (_arg.length >= len) {
return fn.apply(this, _arg)
} else {
return _curry.call(this, fn, len, ..._arg)
}
}
}
let fn = curry(function (a, b, c, d, e) {
console.log(a + b + c + d + e)
})
fn(1, 2, 3, 4, 5) // 15
fn(1, 2)(3, 4, 5)
fn(1, 2)(3)(4)(5)
fn(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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24