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()
我們分別有 hi
、 target
與 name
3 個 argument。
根據 currying 的定義,我們可將一個 function 有 3 個 argument,改寫成 3 個 function 各有 1 個 argument 。
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)
可以抓到 hi
與 target
呢 ? 拜 ECMAScript 的 closure 之賜:
Closure
內層 function 可以直接 reference 到外層 funtion 之變數,而不必靠 parameter 傳入
因此 function(name)
可直接使用 hi
與 target
。
第 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。
let greeting = hi => target => name => hi + ' ' + target + ' ' + name; greeting('Hello')('World')('Sam'); // ?
拜 ES6 之賜,我們有了 arrow function,就不必再使用 巢狀 function
的寫法,程式碼更簡潔,可讀性也變高,這也使得 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 Sam
或 Hello 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 機會更高了。
回想小時候玩樂高積木,哪一種積木最好用 ?
就是顆粒最小的積木最好用,可以說是百搭。
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。
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 -10
與 sum => sum * 0.9
帶入 calculate()
higher order function,正式計算其值。
若我們不將 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()
。
為了使用 function composition,我們會將多個 argument 的 function,currying 成眾多單一 argument 的 function,然後再加以組合。
以上所述就是小编给大家介绍的《ECMAScript 之 Currying》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Flash ActionScript 3.0从入门到精通
章精设、胡登涛 / 清华大学出版社 / 2008-10-1 / 69.00元
Flash ActionScript 3.0的出现,不仅从形式上改变了ActionScript,而且从本质上改变了ActionScript,使ActionScript 3.0成为了真正的面向对象编程语言。 本书从最简单的编程知识出发,带领读者走进编程的大门,是一本不可多得的ActionScript 3.0入门书。本书在注重基础的同时,从更高的层次来介绍ActionScript 3.0的面向对......一起来看看 《Flash ActionScript 3.0从入门到精通》 这本书的介绍吧!