javascript 柯里化

Currying is not idiomatic in JavaScript

1.你說的"不經常使用"是什麼意思?

稍微激進的標題可能會致使標題黨誤讀了這篇博客。我要澄清一下:我本人喜歡函數式編程。個人主要觀點是:
  • 一些核心JavaScript功能與currying相沖突。
  • 在我看來currying還有其餘選擇(尤爲是即將到來的提案)。在partial application(偏函數應用)中使用是很是好的,這沒有什麼衝突。

JavaScript最好的特性之一就是能夠適用多種不一樣的編程風格。所以:若是你Currying編程到沒有任何提到的替代品適合你,那麼你就肆意適用Curring吧。 若是你這樣作,請保持你的代碼風格一致。而且準備好你的代碼與生態系統的其餘部分稍有不一樣。javascript

2.Currying

Currying在函數式編程中是一種流行的技術, 它有助於偏函數應用。html

其思想以下:若是您不爲函數提供全部參數,則返回一個函數。函數的入參是剩餘參數,其輸出是原始函數的結果。java

例如,若是想要數組的全部元素加2,並有一個二進制函數Add(x,y),則能夠以下所示:git

const arr = [1, 2, 3];

arr.map(add(2)); // [3, 4, 5]`

順便說一下,具備自動currying的函數式編程語言一般具備能夠這樣使用的加法操做符。es6

你有兩個選擇執行add()方法來支持currying。github

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)沒有什麼區別。app

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. 擴展閱讀

原文地址


相關文章
相關標籤/搜索