函数柯里化

182 阅读4分钟

柯里化从何而来

柯里化, 即 Currying 的音译。Currying 是编译原理层面实现多参函数的一个技术。

在编码过程中,我们本质上所进行的工作就是——将复杂问题分解为多个可编程的小问题。

Currying 为实现多参函数提供了一个递归降解的实现思路——把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数,在某些编程语言中(如 Haskell),是通过 Currying 技术支持多参函数这一语言特性的。

所以 Currying 原本是一门编译原理层面的技术,用途是实现多参函数。

实现原理

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) // 15
_fn(1)(2)(3)(4)(5) // 15

// 实现,可以把 _curry() 提取出来
function curry(fn, len = fn.length) {
  return function _curry(...args) {
    if (args.length >= len) {
      return fn.apply(this, args)
    }
    return function(...params) {
      _args = [...args, ...params]

      if (_args.length >= len) {
        return fn.apply(this, _args)
      } else {
        return _curry.call(this, ..._args)
      }
    }
  }
}

Currying 使用场景

参数复用

固定不变的参数,实现参数复用是 Currying 的主要用途之一。 上文中的 increment, addTen 是一个参数复用的实例。对 add 方法固定第一个参数为 10 后,改方法就变成了一个将接受的变量值加 10 的方法。

延迟执行

延迟执行也是 Currying 的一个重要使用场景,同样 bind 和箭头函数也能实现同样的功能。 在前端开发中,一个常见的场景就是为标签绑定 onClick 事件,同时考虑为绑定的方法传递参数。

为什么需要 Currying

为了多参函数复用性

Currying 让人眼前一亮的地方在于,让人觉得函数还能这样子复用。

通过一行代码,将 add 函数转换为 increment,addTen 等。

对于 Currying 的复杂实现中,以 Lodash 为列,提供了 placeholder 的神奇操作。对多参函数的复用玩出花样。

import _ from 'loadsh'

function abc(a, b, c) {
  return [a, b, c]
}

var curried = _.curry(abc)

// Curried with placeholders.
curried(1)(_, 3)(2)
// => [1, 2, 3]

为函数式编程而生

Currying 是为函数式而生的东西。应运着有一整套函数式编程的东西,纯函数、compose、container 等等事物。(可阅读《mostly-adequate-guide》 ) 假如要写 Pointfree Javascript 风格的代码,那么 Currying 是不可或缺的。 要使用 compose,要使用 container 等事物,我们也需要 Currying。

为什么不需要 Currying

Currying 的一些特性有其他解决方案

如果我们只是想提前绑定参数,那么我们有很多好几个现成的选择,bind,箭头函数等,而且性能比 Curring 更好。

Currying 陷于函数式编程

在本文中,提供了一个 trueCurrying 的实现,这个实现也是最符合 Currying 定义的,也提供 了 bind,箭头函数等不具备的“新奇”特性——可持续的 Currying(这个词是本人临时造的)。 但是这个“新奇”特性的应用并非想象得那么广泛。 其原因在于,Currying 是函数式编程的产物,它生于函数式编程,也服务于函数式编程。 而 JavaScript 并非真正的函数式编程语言,相比 Haskell 等函数式编程语言,JavaScript 使用 Currying 等函数式特性有额外的性能开销,也缺乏类型推导。 从而把 JavaScript 代码写得符合函数式编程思想和规范的项目都较少,从而也限制了 Currying 等技术在 JavaScript 代码中的普遍使用。 假如我们还没有准备好去写函数式编程规范的代码,仅需要在 JSX 代码中提前绑定一次参数,那么 bind 或箭头函数就足够了。

结论

  1. Currying 在 JavaScript 中是“低性能”的,但是这些性能在绝大多数场景,是可以忽略的。
  2. Currying 的思想极大地助于提升函数的复用性。
  3. Currying 生于函数式编程,也陷于函数式编程。假如没有准备好写纯正的函数式代码,那么 Currying 有更好的替代品。
  4. 函数式编程及其思想,是值得关注、学习和应用的事物。所以在文末再次安利 JavaScript 程序员阅读此书 —— 《mostly-adequate-guide》

部分转载于 juejin.cn/post/684490…