柯里化 (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。读到这里应该会有很多的读者和我抱有同样的疑问:
- 柯里化后的函数与原函数所实现的功能是相同的,我为什么还要多此一举去进行柯里化呢?
- 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 做了以下几件事:
- 通过闭包在返回的函数
$curry中可以继续访问原函数curry和原函数接收的参数的个数arity。 $curry接收的参数个数如果和arity相等,则直接调用原函数即可。$curry接受的参数个数小于arity, 使用 bind 创建一个新的函数并返回。这个函数是复制于$curry,并使用$curry接收的形参作为其默认的形参(理解了 bind 的用法也基本可以理解这句话)。
最后
希望大家可以到 Mostly adequate guide to FP 进行系统的阅读,本文最初的目的也是作为读者的我所进行的一个知识记忆的过程。