ECMAScript 之 Currying

栏目: JavaScript · 发布时间: 5年前

内容简介:不只 OOP 有 Design Pattern,事實上 FP 也有不少 Pattern,而 Currying 算是 FP 最基礎、且用的最多的 Pattern。一些正統 FP 語言,如 Haskell、Clojure、F#、ReasonML … 都在語言內直接支援 Currying;ECMAScript 雖然沒有直接支援,但因為有 First-class Function 與 Closure,使得 Currying 在 ECMAScript 中使用成為可能。ECMAScript 2015Haskell B.

不只 OOP 有 Design Pattern,事實上 FP 也有不少 Pattern,而 Currying 算是 FP 最基礎、且用的最多的 Pattern。一些正統 FP 語言,如 Haskell、Clojure、F#、ReasonML … 都在語言內直接支援 Currying;ECMAScript 雖然沒有直接支援,但因為有 First-class Function 與 Closure,使得 Currying 在 ECMAScript 中使用成為可能。

Version

ECMAScript 2015

Definition

Currying

There is a way to reduce functions of more than one argument to functions of one argument, a way called currying

將一個多 argument 的 function 改寫成多個只有一個 argument 的 function,稱為 currying

Haskell B. Curry

Haskell B. Curry 是位數學家,為了紀念他,Haskell 語言是使用其 ,而 Curry 概念則是使用其

Simple Currying

let greeting = function(hi, target, name) {
    return hi + ' ' + target + ' ' + name;
};

greeting('Hello', 'World', 'Sam'); // ?

我們以最簡單的 Hello World 為例,傳統 function 都會有多個 argument,在 greeting() 我們分別有 hitargetname 3 個 argument。

根據 currying 的定義,我們可將一個 function 有 3 個 argument,改寫成 3 個 function 各有 1 個 argument 。

ECMAScript 之 Currying

let greeting = function(hi) {
  return function(target) {
    return function(name) {
      return hi + ' ' + target + ' ' + name;
    }
  }
};

greeting('Hello')('World')('Sam'); // ?

第 1 行

let greeting = function(hi) {
  return function(target) {
    return function(name) {
      return hi + ' ' + target + ' ' + name;
    }
  }
};

由於 currying 要求每個 function 都只能有 1 個 argument,因此我們必須 return 兩次 function,直到最後一個 return 才會真正回傳值。

為什麼最內層的 function(name) 可以抓到 hitarget 呢 ? 拜 ECMAScript 的 closure 之賜:

Closure

內層 function 可以直接 reference 到外層 funtion 之變數,而不必靠 parameter 傳入

因此 function(name) 可直接使用 hitarget

第 9 行

greeting('Hello')('World')('Sam'); // ?

因此 greeting('Hello') 為只有 1 個 argument 的 function,可再傳入 World

greeting('Hello')('World') 亦為只有 1 個 argument 的 function,可再傳入 Sam

所以 greeting('Hello')('World')('Sam') 其實相當於 greeting('Hello', 'World', 'Sam') ,我們將原本 1 個 function 有 3 個 argument,變成 3 個 function 各有 1 個 argument。

ECMAScript 之 Currying

let greeting = hi => target => name => hi + ' ' + target + ' ' + name;

greeting('Hello')('World')('Sam'); // ?

拜 ES6 之賜,我們有了 arrow function,就不必再使用 巢狀 function 的寫法,程式碼更簡潔,可讀性也變高,這也使得 currying 實用性更高。

ECMAScript 之 Currying

在此亂入一下 F# 的 currying 與 ECMAScript 比較:

let greeting hi target name = hi + " " + target + " " + name

greeting "Hello" "World" "Sam" |> printfn "%s"

F# 也是使用 let 定義 function。

ECMAScript 的 parameter 寫在 = 之後,每個參數以 => 隔開;而 F# 只要在 function 名稱之後以 space 隔開即可。

JavaScript 的 argument 須以 () 一一傳入;而 F# 只要在 function 名稱之後以 space 隔開即可。

ES6 有了 arrow function 之後,可讀性與簡潔性已經與正統 FP 的 F# 差距不遠。

Q:將傳統 function 改寫成 currying 不難,但為什麼要這樣寫呢 ?

的確,要改寫成 currying 並不難,尤其在 ES6 之後,arrow function 使得 currying 寫法非常精簡,也沒有必要再因為 巢狀 function 可讀性不高而排斥 currying。

但回到一個更基本的問題,為什麼要使用 currying 這種 pattern 呢 ? 請耐心看下去,我將一一說明。

Why Currying ?

Reuse Small Function

拆成眾多的小 function,以利後續 code reuse

let greeting = function(hi, target, name) {
  return hi + ' ' + target + ' ' + name;
};

若一次得傳入 3 個 argument,我們只有一個 greeting() function 可用。

let greeting = hi => target => name => hi + ' ' + target + ' ' + name;

若改用 currying 寫法,我們總共有 3 個 function 可用:

greeting()
greeting()()
greeting()()()

在原本 greeting() ,我們要用 reuse,一次就得提供 3 個 argument,否則就無法重複使用。

但 currying 過的 greeting() ,變成了 3 個 function,我們可以依實際需求取用 greeting() ,儘管只有 1 個 parameter,也一樣能夠使用 greeting()

假設我們有個 function,只有 name 為 argument,回傳為 Hello World SamHello World Kevin ,原本 3 個 argument 的 greeting() 就無法被重複使用,但 currying 過的 greeting() 就能被重複使用。

Ramda 提供的 function 都使用 currying 形式,使得其 function 通常有兩種以上用法,一種是求值用,另一種則時傳回 function 當 callback 使用

let greeting = hi => target => name => hi + ' ' + target + ' ' + name;

let helloWorld = greeting('Hello')('World');
helloWorld('Sam'); // ?

第 3 行

let helloWorld = greeting('Hello')('World');

藉由 greeting('Hello')('World') 輕鬆建立新的 helloWorld() ,將來只接受 1 個 argument。

Currying 過的 greeting() ,因為顆粒變小,因此能被 reuse 機會更高了。

ECMAScript 之 Currying

回想小時候玩樂高積木,哪一種積木最好用 ?

就是顆粒最小的積木最好用,可以說是百搭。

Currying 就是把 function 都切成顆粒最小的單一 argument function,因此可藉由 argument 的組合,由一個 function 不斷地組合出新的 function。

Higher Order Function

可以傳入 function 或傳回 function 的 function,通常會將 重複部分 抽成 higher order function,將 不同部分 以 arrow function 傳入

要支援 higher order function 有個前提,語言本身必須支援 first-class function,這在 ECMAScript 很早就支援,所以沒有問題。

let prices = [10, 20, 30];

let calculatePrice1 = prices => {
  let sum = prices  => prices.reduce((acc, elm) => acc + elm);

  return sum(prices) - 10;
};

let calculatePrice2 = prices => {
  let sum = prices  => prices.reduce((acc, elm) => acc + elm);

  return sum(prices) * 0.9
};

calculatePrice1(prices); //?
calculatePrice2(prices); //?

第 3 行

let calculatePrice1 = prices => {
  let sum = prices  => prices.reduce((acc, elm) => acc + elm);

  return sum(prices) - 10;
};

第 9 行

let calculatePrice2 = prices => {
  let sum = prices  => prices.reduce((acc, elm) => acc + elm);

  return sum(prices) * 0.9
};

非常類似,最少已經看到以下這部分重複:

let sum = prices  => prices.reduce((acc, elm) => acc + elm);

return sum(prices)

所以想將這部分抽成 higher order function。

ECMAScript 之 Currying

let prices = [10, 20, 30];

let sum = prices => prices.reduce((acc, elm) => acc + elm);

let calculate = prices => fn => fn(sum(prices));

let calculatePrice = calculate(prices);

calculatePrice(sum => sum - 10); // ?
calculatePrice(sum => sum * 0.9); // ?

第 3 行

let sum = prices => prices.reduce((acc, elm) => acc + elm);

sum() 先抽成 function。

第 5 行

let calculate = prices => fn => fn(sum(prices));

將共用部分抽成 calculate() higher order function,argument 除了原本的 prices 外,還多了 fn ,其中 fn 正是 不同部分

sum(prices) 運算結果傳給 fn() 執行。

第 7 行

let calculatePrice = calculate(prices);

由於 calculate() 已經 currying 過,因此 calculate(prices) 回傳為 funciton。

第 10 行

calculatePrice(sum => sum - 10);
calculatePrice(sum => sum * 0.9);

不同部分 分別以 sum => sum -10sum => sum * 0.9 帶入 calculate() higher order function,正式計算其值。

ECMAScript 之 Currying

若我們不將 calculate() currying 過,則無法傳回 function,只能回傳值,如此就無法將 不同部分 以 arrow function 傳入

Function Composition

將小 function 組合成功能強大的新 function

let prices = [10, 20, 30];

let discount = (rate, prices) => prices.map(elm => elm * rate);

let sum = prices => prices.reduce((acc, elm) => acc + elm);

let compose = (...fns) => fns.reduce((f, g) => (...args) => f(g(...args)));

let fn = compose(sum, discount(0.8));

fn(prices); // ?

第 3 行

let discount = (rate, prices) => prices.map(elm => elm * rate);

宣告 discount() ,使用傳統 2 個 argument 的寫法。

第 5 行

let sum = prices => prices.reduce((acc, elm) => acc + elm);

宣告 sum() ,使用 reduce() 計算 array 的總和。

第 7 行

let compose = (...fns) => fns.reduce((f, g) => (...args) => f(g(...args)));

自己寫一個 compose() ,目的將所有 function 透過 reduce() 組合成一個新的 function。

實務上會使用 Ramda 的 compose() 組合 function

第 9 行

let fn = compose(sum, discount(0.8));

fn(prices); // ?

這裡會出問題,因為 discount() 尚未 currying,必須一次提供 2 個 argument,無法單獨只提供 0.8 一個 argument。

在純 FP 語言如 Haskell、F#、ReasonML 會自動 currying,所以不是問題,但 ECMAScript 必須手動 currying,或者使用 Ramda 的 curry() 將原本的 function 加以 currying

let prices = [10, 20, 30];

let discount = rate => prices => prices.map(elm => elm * rate);

let sum = prices => prices.reduce((acc, elm) => acc + elm);

let compose = (...fns) => fns.reduce((f, g) => (...args) => f(g(...args)));

let fn = compose(sum, discount(0.8));

fn(prices); // ?

第 3 行

const discount = rate => prices => prices.map(elm => elm * rate);

discount() 改成 currying 寫法後,就可以使用 compose()sum()discount() 組合成一個新的 action()

ECMAScript 之 Currying

為了使用 function composition,我們會將多個 argument 的 function,currying 成眾多單一 argument 的 function,然後再加以組合。


以上所述就是小编给大家介绍的《ECMAScript 之 Currying》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Non-Obvious

Non-Obvious

Rohit Bhargava / Ideapress Publishing / 2015-3-29 / USD 24.95

What do Disney, Bollywood, and The Batkid teach us about how to create celebrity experiences for our audiences? How can a vending-machine inspire world peace? Can being imperfect make your business mo......一起来看看 《Non-Obvious》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具