# 柯里化

# 1. 简介

  1. 柯里化(Currying),又称部分求值(Partial Evaluation),是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
  2. 核心思想是把多参数传入的函数拆成单参数(或部分)函数,内部再返回调用下一个单参数(或部分)函数,依次处理剩余的参数。 按照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

# 常见用法

  • 参数复用

通过柯里化方法,缓存参数到闭包内部参数,然后在函数内部将缓存的参数与传入的参数组合后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
  • 提高适用性

通用函数解决了兼容性问题,但同时也会再来,使用的不便利性,不同的应用场景往,要传递很多参数,以达到解决特定问题的目的。有时候应用中,同一种规则可能会反复使用,这就可能会造成代码的重复性。


// 未柯里化前
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

可以看到这里柯里化方法的使用和偏函数比较类似,顺便回顾一下偏函数~ 偏函数是创建一个调用另外一个部分(参数或变量已预制的函数)的函数,函数可以根据传入的参数来生成一个真正执行的函数。比如:

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
  • 延迟执行 柯里化的另一个应用场景是延迟执行。不断的柯里化,累积传入的参数,最后执行。例如累加
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

更通用的写法,将处理函数提取出来:

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

经典面试

如何实现下面输出结果都一样 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
最后更新时间: 6/24/2023, 7:42:46 PM