原文來自 Currying in JavaScript(https://blog.bitsrc.io/understanding-currying-in-javascript-ceb2188c339)javascript
函數式編程是一種將函數做爲參數來傳遞和返回的,而且沒有反作用(只是返回新的值,不改變系統變量)的編程方式。因此不少語言採納了這種編程方式,這裏面JavaScript、Haskell、Clojure·、Erlang和Scala是最受歡迎的。java
而且因爲它可以傳遞和返回函數,它也帶來了許多概念:npm
在這裏咱們要來探索的一個概念是柯里化(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函數被部分應用於接受比原函數更少的參數。
若是要把它柯里化,它看起來會是這樣的:
柯里化根據函數的參數數量來建立嵌套函數,每一個函數接受一個參數,若是沒有參數的話也就不存在柯里化。
這裏存在一種狀況柯里化和部分應用會彼此相遇,讓咱們來看一個函數:
它的柯里化和部分應用都是這樣的:
雖然柯里化和部分應用給出了同樣的結果,但它們是兩個不一樣的實體。
就像咱們以前說過的,柯里化和部分應用是有關聯的,但其實設計上並不同。它們之間的共通點是他們都依靠閉包來工做。
固然有用,柯里化用來:
舉個例子,你有一家商店,而後你想給你的優惠顧客10%的折扣:
當一個優惠顧客消費了500元,你會給他:
從長遠的看,你會發現你天天都要計算10%的折扣。
咱們能將這個函數柯里化,而後咱們就不用每次都寫那0.10了:
如今,咱們只需用商品價格來計算就能夠了:
接下來,有些優惠顧客愈來愈重要,讓咱們稱爲vip顧客,而後咱們要給20%的折扣,咱們這樣來使用柯里化了的discount函數:
咱們爲vip顧客使用0.2調用柯里化discount函數來配置了一個新的函數。這個twentyPercentDiscount函數會被用來計算vip顧客的折扣:
好比咱們有個用來計算體積的函數:
碰巧你倉庫裏的全部物品都是100m高。你會看到你不停地用h=100來調用這個函數:
爲了解決這個問題,你把volume函數柯里化(像咱們以前作過的):
咱們能給同類物品定義一個特殊函數:
讓咱們創建一個函數來接受任何函數而且返回柯里化版本的函數。
爲此咱們須要這樣作(這裏是做者的簡單版本,看看就行了,不做爲參考。你會有不同的作法):
咱們在這裏作了什麼?咱們的curry函數接受一個咱們想要柯里化的函數(fn)和一個變量(...args)。這裏的rest操做符用來將參數彙集成一個...args。接下來咱們返回一個函數,該函數將其他參數收集爲..._args。此函數經過spread運算符將... args和..._ args做爲參數解構傳入來調用原始函數fn,而後將值返回給用戶。
讓咱們使用咱們的curry函數來建立一個特殊的函數(一個專門用來計算100m長度的物品體積):
閉包使在JavaScript中進行柯里化成爲可能。它可以保留已經執行的函數的狀態,使咱們可以建立工廠函數(能夠爲其參數添加特定值的函數)。
柯里化、閉包和函數式編程是很是棘手的。 但我向你保證只要花時間不斷地練習,你會開始掌握它,看到它是多麼值得。