函数柯里化
柯里化是什么
在计算机科学中,柯里化(Currying),又译为卡瑞化或加里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
柯里化不会调用函数,它只是对函数进行转换。
函数柯里化的基本方法和函数绑定 bind() 一样(唯一不同是 bind
强制绑定了 context
):使用一个闭包返回一个函数。两者的区别在于,当函数被调用时,返回的函数还需要设置一些传入的参数。例如:一个函数从可调用的 fn(a, b, c)
转换成可调用的 fn(a)(b)(c)
。
柯里化的实现
首先看个简单的例子:
function curry (fn) {
return function (a) {
return function (b) {
return fn(a, b)
}
}
}
function sum (a, b) {
return a + b
}
var sumCurry = curry(sum)
console.log(sumCurry(2)(3)) // 5
分析:curry(sum)
执行结果是 function (a)
,当它被 sumCurry(2)
这样调用时,它的参数会被保存在词法作用域中(闭包),然后返回一个 function (b)
,接着这个函数被 sumCurry(3)
调用,并且,它将该调用传递给原始的 sum
函数。
其实上面的 curry()
可以优化成如下:
function curry (fn) {
var args = [].slice.call(arguments, 1)
return function () {
var newArgs = args.concat([].slice.call(arguments))
return fn.apply(this, newArgs)
}
}
如果 sum(a, b, c)
携带 3 个参数呢?curry()
函数还要再返回 function (c)
。如果 sum
携带 4、5 个参数呢?显然上面那种写法会严重影响阅读体验。为了解决这个问题,请看下面代码:
// help_curry() 是上面 curry() 优化后作为协助的函数
function help_curry (fn) {
var args = [].slice.call(arguments, 1)
// debugger;
return function () {
var newArgs = args.concat([].slice.call(arguments))
// debugger;
return fn.apply(this, newArgs)
}
}
function curry (fn, len) {
len = len || fn.length
// debugger;
return function () {
if (arguments.length < len) {
var unite = [fn].concat([].slice.call(arguments))
// debugger;
return curry(help_curry.apply(this, unite), len - arguments.length)
} else {
// debugger;
return fn.apply(this, arguments)
}
}
}
var fn = curry(function (a, b, c) {
return [a, b, c]
})
console.log(fn('a', 'b')('c')) // ["a", "b", "c"]
为了更好的理解上面逻辑,建议把我注释的 debugger
放开,在浏览器的调试面板查看。文中涉及到的 call
和 apply
方法,如果不了解的,可以戳这里学习。
注意
由于文中须要使用到函数的长度 fn.length
,不建议使用 ES6 给参数定义默认值,因为会导致调用函数错误。详见
柯里化的应用
现在我们已经了解了柯里化的定义以及如何实现,那它到底有什么用呢?
例如,我们有一个用于格式化和输出信息的日志(log)函数 log(date, importance, message)
,见下面示意代码:
function log (date, importance, message) {
console.log(`[${date.getHours()}:${date.getMinutes()}] [${importance}] ${message}`)
}
var log = curry(log) // 柯里化
var logNow = log(new Date())
logNow("WARN", "this is warn") // HH:mm WARN this is warn
var debugNow = logNow("DEBUG")
debugNow("this is debug") // HH:mm DEBUG this is debug
柯里化的这种用途理解为参数复用。
结语
这篇文章主要是认识函数柯里化的基本概念,代码实现和它的基础用途。这里虽然实现了 curry()
函数,但更像柯里化和偏函数的综合应用。(具体什么是偏函数,下一篇文章介绍)
柯里化是生于函数式编程,也服务于函数式编程,而 JavaScript 并非真正的函数式编程语言,相比 Haskell 等函数式编程语言,JavaScript 使用柯里化等函数式特性有额外的性能开销,也缺乏类型推导。
因此限制了柯里化在 JavaScript 实际项目中的普遍使用。
参考文献
- JavaScript专题之函数柯里化
- 《JavaScript高级程序设计——函数柯里化》
- 柯里化——现代JavaScript教程
- 大佬,JavaScript 柯里化,了解一下?