Currying is not idiomatic in JavaScript
1.你説的"不常用"是什麼意思?
稍微激進的標題可能會導致標題黨誤讀了這篇博客。我要澄清一下:我本人喜歡函數式編程。我的主要觀點是:
- 一些核心JavaScript功能與currying相沖突。
- 在我看來currying還有其他選擇(尤其是即將到來的提案)。在partial application(偏函數應用)中使用是非常好的,這沒有什麼衝突。
JavaScript最好的特性之一就是可以適用多種不同的編程風格。因此:如果你Currying編程到沒有任何提到的替代品適合你,那麼你就肆意適用Curring吧。 如果你這樣做,請保持你的代碼風格一致。並且準備好你的代碼與生態系統的其他部分稍有不同。
2.Currying
Currying在函數式編程中是一種流行的技術, 它有助於偏函數應用。
其思想如下:如果您不為函數提供所有參數,則返回一個函數。函數的入參是剩餘參數,其輸出是原始函數的結果。
例如,如果想要數組的所有元素加2,並有一個二進制函數Add(x,y),則可以如下所示:
const arr = [1, 2, 3];
arr.map(add(2)); // [3, 4, 5]`
順便説一下,具有自動currying的函數式編程語言通常具有可以這樣使用的加法操作符。
你有兩個選擇執行add()方法來支持currying。
2.1 簡單 currying
ES6箭頭函數可以很容易地手動編寫柯里函數:
const add = x => y => x + y;
// 等價於(譯者注)
function add(x) {
return function(y) {
return x + y;
}
}
如下所示你可以這樣執行add()
add(2)(3); // 5
一個函數持有多個需要轉化的嵌套函數,多數具有自動currying的函數式編程語言在語法上add(1, 2) 和add(1)(2)沒有什麼區別。
2.2 重載 currying
一些庫提供了重載的函數。 根據參數數量,這些重載函數中的每一個都會有不同的表現:
- 如果只使用一個參數,將會柯里化。
- 如果使用所有的參數,將會是一個普通的函數調用。
- 如果使用了多個參數(而不是所有參數),則會返回一個綁定到這些參數的函數。
這種形式的curring的實現如下。
function add(...args) {
if (args.length < 2) {
return add.bind(this, ...args);
}
const [x, y] = args;
return x + y;
}
可以編寫一個工具函數,將普通函數轉換為這種實現。
2.3 小結
柯里化是一種有助於偏函數應用的函數轉化技術。
3. Currying 和一些javascript基礎有衝突
如果想要在JavaScript使用currying編程你有兩選擇:
-
使用真currying
- 提倡:容易靜態輸入。
- 反對:不常用的函數調用語法。
-
使用重載currying
- 提倡:優雅的語法。
- 反對:難以取得的靜態類型。
如果你轉換了內置函數或方法來適應你的currying風格,那麼你將面對很多問題。
注意:並非所有這些缺點同樣重要。
3.1 具名參數
javascript中具名參數可以通過Object字面量模擬(Simulating named parameters ES6中)我喜歡這種綁定參數的方式,他使得代碼更有自我描述性。不是所有currying的javascript庫都支持具名參數。
3.2 不清楚函數調用和非函數調用
通常情況下在函數的後面使用()就可以執行該函數。Currying後你必須清楚的知道函數函數foo中函數的個數。以便知道 foo(123)執行後得到的是一個函數還是函數的結果。
比較下面兩個偏函數應用
foo(123, ?)
_ => foo(123, _)
foo()支持多種方式調用。
使用具名參數,在curry中會丟失更多信息
arr.map(findCity({ latitude: 48.137154 })); // curried
arr.map(_ => findCity({ latitude: 48.137154, longitude: _ }))
靜態類型系統(TypeScript,Flow,...)會減少錯誤,但是替代方法仍然更易於閲讀。
3.3 Currying 與默認參數的衝突
如果忽略參數,就會使用函數的默認參數
function add(x=0, y=0) {
return x + y;
}
默認參數可以使的添加參數更加透明並且不會破壞現有調用,這種技術對命名參數特別有用,我經常用來保證通用函數和方法的適用性。
使用參數默認值省略參數意味着使用默認值,使用currying省略參數意味着返回偏函數, 因此兩者有衝突。
3.4 Currying 友好的函數
使用Currying,參數對函數開始是對配置和結束運算都很重要。而Javascript中並不是這樣。
例如parseInt()的簽名是:
parseInt(s : string, radix : number);
這使得它不可能這樣預先填充基數:
['32', '17', '5'].map(parseInt(10)) // doesn’t work
偏函數編程有那些替代方案?
當談到Currying,人們通常想要的是偏函數編程。讓我們看看下面的例子重載Currying並探索替代方案。
[1, 2, 3].map(add(2))
替代 1: .bind().
[1, 2, 3].map(add.bind(null, 2))
替代 2: arrow functions.
[1, 2, 3].map(x => add(2, x))
替代 3: an upcoming proposal for partial application.
[1, 2, 3].map(add(2, ?))
注:語法固定,提案處於早期階段。
這個建議的好處在於,它可以很好地處理任意的簽名,並且具有很好的描述性語法。
5. 擴展閲讀
- Currying versus partial application (with JavaScript code)
- Arrow functions vs. bind()
- Uncurrying “this” in JavaScript
原文地址
- 我的blog: neverland.github.io
- 我的email enix@foxmail.com