柯里化(Currying)

130 阅读3分钟

柯里化 (Currying)

理解什么是柯里化

按照 Mostly adequate guide to FP 作者所述:

You can call a function with fewer arguments than it expects. It returns a function that takes the remaining arguments.

如一个接收四个参数的函数 fn(para1, para2, para3, para4), 当传入前两个参数进行调用时,他会返回一个函数来处理剩下的两个参数。fn(a, b) => nf(para3, para4)

一个更加具体的例子:

const add = x => y => x + y
const increment = add(1)
const addTen = add(10)

increment(2)
addTen(2)

在这里 add 处理掉参数 x, 并返回一个函数来处理 y

柯里化工具

我们可以使用一些柯里化工具来将一个普通的函数柯里化。例如上文作者所提供的 curry 工具。

先来看看,他可以帮我们做些什么

const fn = (what, str) => str.match(what)
const match = curry(fn)

将函数 fn 柯里化后返回给 match。调用这两个函数会有以下的结果:

fn(/\s+/ig, 'hello world')  // [' ']

match(/\s+/ig, 'hello world') // [' ']

// 这里 match 返回一个新的函数,来处理剩下的参数 `str`
match(/\s+/ig) // (str) => str.match(/\s+/ig)

match(/\s+/ig)('hello world') // [' ']

注意到第二种调用 match 的方式了吗? 这和上文提到的 add 函数是很类似的。首先由 match 处理掉参数 what (对应 add 处理掉参数 x), 然后 match 返回一个函数 (str) => str.match(/\s+/ig) 来处理参数 str (对应 add 返回 y => x + y 来处理 y)。看到这里让们回忆一下柯里化的定义:

You can call a function with fewer arguments than it expects. It returns a function that takes the remaining arguments.

本文许多部分(包括示例)是引用的 Mostly adequate guide to FP。读到这里应该会有很多的读者和我抱有同样的疑问:

  1. 柯里化后的函数与原函数所实现的功能是相同的,我为什么还要多此一举去进行柯里化呢?
  2. curry 工具是怎么做的?让柯里化后的函数能够像上文 match 函数一样的方式来处理参数。

第一个问题,让我们一起想像一下选择钱包还是移动支付,我想大家应该就知道答案了。许多东西正是日用而不觉,当有一天突然离开我们的时候,才发现自己的生活变得多么的狼狈。

第二个问题,让我们一起看看 curry 工具的实现方式。

curry 的实现方式

function curry (fn) {
  const arity = fn.length

  return function $curry (...args) {
    if (args.length < arity) {
      return $curry.bind(null, ...args)
    }

    return fn.call(null, ...args)
  }
}

如果大家和我一样看到源码的时候还有一些不熟悉的 api, 可以参考 MDN

curry 做了以下几件事:

  1. 通过闭包在返回的函数 $curry 中可以继续访问原函数 curry 和原函数接收的参数的个数 arity
  2. $curry 接收的参数个数如果和 arity 相等,则直接调用原函数即可。
  3. $curry 接受的参数个数小于 arity, 使用 bind 创建一个新的函数并返回。这个函数是复制于 $curry ,并使用 $curry 接收的形参作为其默认的形参(理解了 bind 的用法也基本可以理解这句话)。

最后

希望大家可以到 Mostly adequate guide to FP 进行系统的阅读,本文最初的目的也是作为读者的我所进行的一个知识记忆的过程。