【译】理解JavaScript中的柯里化
译文开始
函数式编程是一种编程风格,这种编程风格就是试图将传递函数作为参数(即将作为回调函数)和返回一个函数,但没有函数副作用(函数副作用即会改变程序的状态)。
有很多语言采用这种编程风格,其中包括javascript、haskell、clojure、erlang和scala等一些很流行的编程语言。
函数式编程凭借其传递和返回函数的能力,带来了许多概念:
- 纯函数
- 柯里化
- 高阶函数
其中一个我们将要看到的概念就是柯里化。
在这篇文章,我们将看到柯里化是如何工作以及它如何在我们作为软件开发者的工作中发挥作用。
什么是柯里化
柯里化是函数式编程中的一种过程,可以将接受具有多个参数的函数转化为一个的嵌套函数队列,然后返回一个新的函数以及期望下一个的内联参数。它不断返回一个新函数(期望当前参数,就像我们之前说的那样)直到所有参数都用完为止。这些参数会一直保持“存活”不会被销毁(利用闭包的特性)以及当柯里化链中最后的函数返回并执行时,所有参数都用于执行。
柯里化就是将具有多个arity的函数转化为具有较少的arity的函数。——
备注:术语arity(元数):指的是函数的参数个数,例如:
function fn(a, b) { //... } function _fn(a, b, c) { //... }
函数fn有两个参数(即 2-arity函数)以及_fn有三个参数(即3-arity函数)。
因此,柯里化将一个具有多个参数的函数转化为一系列只需一个参数的函数。
下面,我们看一个简单的例子:
function multiply(a, b, c) { return a * b * c; }
这个函数接收三个数字并且相乘,然后返回计算结果。
multiply(1,2,3); // 6
接下来看看,我们如何用完整参数调用乘法函数。我们来创建一个柯里化版本的函数,然后看看如何在一系列调用中调用相同的函数(并且得到同样的结果)。
function multiply(a) { return (b) => { return (c) => { return a * b * c } } } log(multiply(1)(2)(3)) // 6
我们已经将multiply(1,2,3)函数调用形式转化为multiply(1)(2)(3)多个函数调用的形式。
一个单独的函数已经转化为一系列的函数。为了得到三个数字1、2、3的乘法结果,这些数字一个接一个传递,每个数字会预先填充用作下一个函数内联调用。
我们可以分开这个multiply(1)(2)(3)函数调用步骤,更好理解一点。
const mul1 = multiply(1); const mul2 = mul1(2); const result = mul2(3); log(result); // 6
我们来一个接一个地传递参数。首先传参数1到multiply函数:
let mul1 = multiply(1);
以上代码执行会返回一个函数:
return (b) => { return (c) => { return a * b * c } }
现在,变量mul1会保持以上的函数定义,这个函数接收参数b。
我们调用函数mul1,传入参数2:
let mul2 = mul1(2);
函数mul1执行后会返回第三个函数
return (c) => { return a * b * c }
这个返回的函数现在保存在变量mul2中。
本质上,变量mul2可以这么理解:
mul2 = (c) => { return a * b * c }
当传入参数3调用函数mul2时,
const result = mul2(3);
会使用之前传入的参数进行计算:a=1,b=2,然后结果为6。
log(result); // 6
作为一个嵌套函数,mul2函数可以访问外部函数的变量作用域,即multiply函数和mul1函数。
这就是为什么mul2函数能使用已经执行完函数中定义的变量中进行乘法计算。虽然函数早已返回而且已经在内存中执行垃圾回收。但是它的变量还是以某种方式保持“存活”。
备注:以上变量保持存活是闭包特性,不明白可以查看闭包相关文章了解更多
你可以看到三个数字每次只传递一个参数应用于函数,并且每次都返回一个新的函数,值得所有的参数用完为止。
下面来看一个其他的例子:
function volume(l,w,h) { return l * w * h; } const acylinder = volume(100,20,90) // 180000
上面是一个计算任何实心形状体积的函数。
这个柯里化版本将接受一个参数以及返回一个函数,该函数同样也接受一个参数和返回一个新的函数。然后一直这样循环/继续,直到到达最后一个参数并返回最后一个函数。然后执行之前的参数和最后一个参数的乘法运算。
function volume(l) { return (w) => { return (h) => { return l * w * h } } } const acylinder = volume(100)(20)(90) // 180000
就像之前的multiply函数那样,最后的函数只接受一个参数h,但是仍然会对那些早已执行完返回的函数作用域中里的其他变量执行操作。能这样操作是因为有闭包的特性。
译者注:以上写的很啰嗦,感觉另外的例子完全就是重复说明。
柯里化背后的想法其实是获取一个函数并派生出一个返回特殊函数的函数。
柯里化在数学方面的应用
我有点喜欢数学说明