JavaScript中的柯里化(Currying in JavaScript)[翻译]

原文来自 Currying in JavaScript(https://blog.bitsrc.io/understanding-currying-in-javascript-ceb2188c339)javascript

函数式编程是一种将函数做为参数来传递和返回的,而且没有反作用(只是返回新的值,不改变系统变量)的编程方式。因此不少语言采纳了这种编程方式,这里面JavaScript、Haskell、Clojure·、Erlang和Scala是最受欢迎的。java

而且因为它可以传递和返回函数,它也带来了许多概念:npm

  • 纯函数(Pure Functions)
  • 柯里化(Currying)
  • 高阶函数(Higher-order functions)

在这里咱们要来探索的一个概念是柯里化(Currying)。编程

在本文中,咱们未来了解柯里化是如何工做的,而且在开发过程当中有何用途。闭包

 

|什么是柯里化

柯里化是把接受多个参数的函数转换成接受单一参数的函数的操做。它返回接受余下的参数并且返回结果的新函数。less

它持续地返回一个新函数直到全部的参数用尽为止。这些参数所有保持“活着”的状态(经过闭包),而后当柯里化链中的最后一个函数被返回和执行时会所有被用来执行。函数式编程

Currying is the process of turning a function with multiple arity into a function with less arity(柯里化是将一个多元函数转换为低元函数的操做)—Kristina Brainwave函数

这里的‘元’,指的是函数所须要的参数数量,举个例子:spa

函数fn接受2个参数(2元函数),_fn接受3个参数(3元函数)。因此,柯里化将一个多参数的函数转换成一系列只接受单个参数的函数。设计

让咱们来看个简单的例子:

这个函数接受3个数字,相乘并返回结果。咱们用了所有的参数来调用这个乘法函数。让咱们来建立一个柯里化版本的函数,而后来看看我们是如何调用这个相同的函数(和返回相同的结果):

这里咱们将multiply(1,2,3)调用变成了multiply (1) (2) (3) 调用。

单独一个函数被转换成了一系列函数。为了获得数字一、二、3相乘的结果,这些数字被一个接一个地传递,每一个数字预填了下一个函数内联调用。

咱们把multiply (1) (2) (3) 分割一下来帮助理解:

当咱们把1传递给multiply函数时,它返回了这个函数:

如今,变量mul1拿到了上述这个须要参数b的函数定义。咱们调用mul1函数并传2进去,它会返回下面这第三个函数:

这个返回的函数如今存进了mul2变量里,实质上,mul2就至关于:

当mul2使用3做为参数调用时,它一块儿使用了以前已拿到的参数a=1和b=2进行运算并返回结果6。

做为一个嵌套函数,mul2可以访问到外部的两个函数multiply和mul1的做用域。这就是为何mul2能利用定义在已经‘离场’的函数中的参数来进行乘法操做的缘由。即便这些函数早已返回而且从内存中垃圾回收了,但其变量仍然保持‘活着’。你能够看到3个数字每次只有1个提供给函数,而且同一时间里一个新函数会被返回,直到全部的数字用尽为止。

让咱们来看另外一个例子:

咱们有一个函数用来计算固体的体积。柯里化版本的它会接受一个参数并返回一个函数,一直这样运行直到最后一个参数到达和最后一个函数被返回,而后再把最后一个参数与以前的参数进行乘法操做:

就和咱们在multiply函数中同样,最后一个函数只接受h参数,可是会与其余那些早已返回的函数做用域中的参数一块儿相乘,是闭包使这一切成为可能。

柯里化背后的思想就是获取一个函数并派生出一个返回特殊函数的函数。

 

|数学中的柯里化

我很喜欢数学举证,你能够到维基百科里找柯里化概念的进一步解释。让咱们来用本身的例子看看,若是咱们有一个等式:

这里有x和y两个变量。若是x=3而且y=4,让咱们来计算一下y:

获得告终果z=13。咱们能柯里化 f (x, y) 来提供变量给一系列函数:

若是咱们在等式hx(y) = x^2 + y中固定x=3,它会返回一个以y为变量的新等式:

跟这个等价:

最终结果尚未肯定下来,它只是返回了一个新等式(9+y)而且等待着另外一个参数y。接下来咱们传递y=4:

y是变量链条中的最后一个,与以前仍保留着的变量x=3作加法运算,得出告终果13。

基本上,咱们将方程f(x,y)= 3 ^ 2 + y柯里化为一系列方程式:

这里若是你以为不够清楚的话,能够到这(https://en.m.wikipedia.org/wiki/Currying)看详情。

 

|柯里化和部分函数应用

如今,有些人可能开始在想柯里化函数的嵌套函数数量取决于它接收的参数数量。可是我也能这样来设计个人体积计算的柯里化函数:

因此它能被这样来调用:

咱们只是定义了一个特殊函数用来计算长度为70的任何物体的体积。这里有3个参数和2个嵌套函数,不像咱们以前的版本有3个参数和3个嵌套函数。这个版本不能称之为柯里化,咱们只是作了volume函数的部分应用

柯里化和部分应用是有关联的,可是它们也有些不一样的概念。

部分应用将一个函数转换为另外一个更少参数的函数:

注意:我故意省略了performOp函数的实现。由于这里没有必要。 全部你须要了解的是柯里化和部分应用背后的概念。

这是acidityRatio函数的部分应用,这里没有涉及到柯里化。 acidityRatio函数被部分应用于接受比原函数更少的参数。

若是要把它柯里化,它看起来会是这样的:

柯里化根据函数的参数数量来建立嵌套函数,每一个函数接受一个参数,若是没有参数的话也就不存在柯里化。

这里存在一种状况柯里化和部分应用会彼此相遇,让咱们来看一个函数:

它的柯里化和部分应用都是这样的:

虽然柯里化和部分应用给出了同样的结果,但它们是两个不一样的实体。

就像咱们以前说过的,柯里化和部分应用是有关联的,但其实设计上并不同。它们之间的共通点是他们都依靠闭包来工做。

|柯里化有用吗?

固然有用,柯里化用来:

1. 编写能够轻松复用和配置的小代码块,就像咱们使用npm同样:

举个例子,你有一家商店,而后你想给你的优惠顾客10%的折扣:

当一个优惠顾客消费了500元,你会给他:

从长远的看,你会发现你天天都要计算10%的折扣。

咱们能将这个函数柯里化,而后咱们就不用每次都写那0.10了:

如今,咱们只需用商品价格来计算就能够了:

接下来,有些优惠顾客愈来愈重要,让咱们称为vip顾客,而后咱们要给20%的折扣,咱们这样来使用柯里化了的discount函数:

咱们为vip顾客使用0.2调用柯里化discount函数来配置了一个新的函数。这个twentyPercentDiscount函数会被用来计算vip顾客的折扣:

2. 避免频繁调用具备相同参数的函数:

好比咱们有个用来计算体积的函数:

碰巧你仓库里的全部物品都是100m高。你会看到你不停地用h=100来调用这个函数:

为了解决这个问题,你把volume函数柯里化(像咱们以前作过的):

咱们能给同类物品定义一个特殊函数:

|通用的柯里函数

让咱们创建一个函数来接受任何函数而且返回柯里化版本的函数。

为此咱们须要这样作(这里是做者的简单版本,看看就行了,不做为参考。你会有不同的作法):

咱们在这里作了什么?咱们的curry函数接受一个咱们想要柯里化的函数(fn)和一个变量(...args)。这里的rest操做符用来将参数汇集成一个...args。接下来咱们返回一个函数,该函数将其他参数收集为..._args。此函数经过spread运算符将... args和..._ args做为参数解构传入来调用原始函数fn,而后将值返回给用户。

让咱们使用咱们的curry函数来建立一个特殊的函数(一个专门用来计算100m长度的物品体积):

 

结语

闭包使在JavaScript中进行柯里化成为可能。它可以保留已经执行的函数的状态,使咱们可以建立工厂函数(能够为其参数添加特定值的函数)。

柯里化、闭包和函数式编程是很是棘手的。 但我向你保证只要花时间不断地练习,你会开始掌握它,看到它是多么值得。

相关文章
相关标签/搜索