譯文開始 函數式編程是一種編程風格,這種編程風格就是試圖將傳遞函數作為參數(即將作為回調函數)和返回一個函數,但沒有函數副作用(函數副作用即會改變程式的狀態)。 有很多語言採用這種編程風格,其中包括JavaScript、Haskell、Clojure、Erlang和Scala等一些很流行的編程語言。 ...
譯文開始
函數式編程是一種編程風格,這種編程風格就是試圖將傳遞函數作為參數(即將作為回調函數)和返回一個函數,但沒有函數副作用(函數副作用即會改變程式的狀態)。
有很多語言採用這種編程風格,其中包括JavaScript、Haskell、Clojure、Erlang和Scala等一些很流行的編程語言。
函數式編程憑藉其傳遞和返回函數的能力,帶來了許多概念:
- 純函數
- 柯里化
- 高階函數
其中一個我們將要看到的概念就是柯里化。
在這篇文章,我們將看到柯里化是如何工作以及它如何在我們作為軟體開發者的工作中發揮作用。
什麼是柯里化
柯里化是函數式編程中的一種過程,可以將接受具有多個參數的函數轉化為一個的嵌套函數隊列,然後返回一個新的函數以及期望下一個的內聯參數。它不斷返回一個新函數(期望當前參數,就像我們之前說的那樣)直到所有參數都用完為止。這些參數會一直保持“存活”不會被銷毀(利用閉包的特性)以及當柯里化鏈中最後的函數返回並執行時,所有參數都用於執行。
柯里化就是將具有多個arity的函數轉化為具有較少的arity的函數。——kbrainwave
備註:術語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,但是仍然會對那些早已執行完返回的函數作用域中里的其他變數執行操作。能這樣操作是因為有閉包的特性。
譯者註:以上寫的很啰嗦,感覺另外的例子完全就是重覆說明。
柯里化背後的想法其實是獲取一個函數並派生出一個返回特殊函數的函數。
柯里化在數學方面的應用
我有點喜歡數學說明