本系列文章,內容以探討 Kyle Simpson. Functional-Light JavaScript 一書內容為主
- 目標:是讀懂 FP,能用 code 與人交流,而不是被壓在 FP 的術語大山下喘不過氣。
- 原文地址:Functional-Light JavaScript
回想昨天的範例,
function sum(...args) {
var sum = 0;
for (let i = 0; i < args.length; i++) {
sum += args[i];
}
return sum;
}
sum( 1, 2, 3, 4, 5 );
// 運用 Currying
// 因為是不定長度參數 ...args,需指定個數
var curriedSum = curry( sum, 5 );
curriedSum( 1 )( 2 )( 3 )( 4 )( 5 ); // 15
範例1 Currying 後的 curriedSum
假設我們今天不借助 curry 直接手刻 curriedSum 呢,如何寫呢?
function curriedSum(v1) {
return function(v2){
return function(v3){
return function(v4){
return function(v5){
return sum( v1, v2, v3, v4, v5 );
};
};
};
};
}
範例2 視覺化 Curry
很醜的巢狀,但這是一個徹底觀察 curry 到底發生什麼事的好方式,將一個多參數函數拆解成一串連續的函數,每一層巢狀會 return 另一個函數處理下一個參數,一直持續直到收到所有參數為止。
也改成 ES6 的版本看看,風格各有優缺,端看個人取向
curriedSum =
v1 =>
v2 =>
v3 =>
v4 =>
v5 =>
sum( v1, v2, v3, v4, v5 );
// 一行式
curriedSum = v1 => v2 => v3 => v4 => v5 => sum( v1, v2, v3 v4, v5 )
範例3 視覺化 Curry ES6 版本
好,連續兩天的語法討論以及重構,到底為什麼要這樣做?為什麼要把 sum 改成這樣:
sum(1)(2)(3)
partial(sum,1,2)(3)
這兩種風格都比原來的簽名 (signature) sum(1,2,3)
怪,請讓我試著解釋
原因ㄧ,兩種方式可以讓你切割傳參數的時空背景(時間、程式的不同區塊),原本的方式需要你在呼叫的當下立刻傳入所有的參數,如果你的函數中有些參數待會才傳入,可以考慮使用 currying 和 partial application。
原因二,就是方便函數組合 (Composition),在接下的連載會討論到,簡單解釋:f(x)
和 g(x)
如果要合成為 f(g(x))
,有一個隱藏的假設,就是 f(x)
和 g(x)
都只能接受一個參數。如果是多個参数,比如 f(x, y)
和 g(a, b, c)
,就很難實現函數組合。藉由 currying 和 partial application 調整成 Arity = 1
將可方便實現。
最重要的原因,也就是本文的主旨:提高可讀性。那...到底為什麼這樣的重構可以提升可讀性?回想我們的 ajax(url, data, cb)
的範例:
ajax (
"http://some.api/order",
{ id: ORDER_ID },
function handler(order) { // ... }
)
直接使用 ajax
,在呼叫的當下,就需要準備所有會用到的參數,如果這些參數改成在不同時機傳入,語意又是如何呢?
var getOrder = partial(
ajax,
"http://some.api/order",
{ id: ORDER_ID }
);
// 待要處理 Order 時,再叫
getOrder( function handler(order){ /* .. */ } );
範例4
範例4中,我們先定義出 getOrder ,並僅將此刻需要的參數傳入 url
、data
,當待會要處理訂單時,再呼叫 getOrder ,並同時把此刻需要的 handler 傳入。
這就是所謂的抽象化 (abstraction),我們將一句 ajax
呼叫,拆成兩個細節:
不管用 currying 和 partial application ,傳入部分的參數,都會 return 另一個 closure 已傳入參數的 特定性更強的函數 (SPECIALIZED function,特殊化函數),但別忘記抽象化的目的,隱藏細節,增強可讀性。