一般的function
如果今天有一個需求是,要實作簡單的字串連結 function
會使用到的輸出是:
1
| concatString('又舔', '嘴唇');
|
你可能會這樣設計:
1 2 3 4 5 6
| function concatString(a, b) { return a + b; };
const result = concatString('又舔', '嘴唇'); console.log('result: ', result);
|
簡單的currying實作
天呀,你太小看我了吧筆者?出這種簡單的東西
那來換個方式問好了,如果是這樣你會怎麼做:
1 2
| const result = concatString('又舔')('嘴唇'); console.log('result: ', result);
|
這個時候平時沒多注意的人可能就會愣住了,冷靜下來觀察一下
什麼樣的情況才會用到最右邊的2,那就是左邊必須是function
因此 sum(1) 是一個function,而且一次只能取得一個參數
所以就是function回傳function的概念,最終輸出結果。
1 2 3 4 5
| function concatString(a) { return function(b) { return a+b; } };
|
currying的設計
currying的第一關過了之後,基本上你就可以設計很多情境的currying了
不過還有一個問題,如果今天突然收到一些設計上的需求,要設計可以呼叫多次的狀況呢:
1
| concatString('又舔')('又舔')('又')('又舔')('嘴唇');
|
迷A:那還不簡單?照做呀:
1 2 3 4 5 6 7 8 9 10 11
| function concatString(a) { return function(b) { return function(c) { return function(d) { return function(e) { return a+b+c+d+e; } } } } };
|
迷B:嗯…看起來是可以用的,不過你明天可以不用來了(X
基本上這邊會有幾個問題:
- callback hell
- 彈性不夠
上面我們提到實作Currying的過程是return一個function
重複的事情你會想到什麼?iterative或是recursive
這邊我們就用recursive來設計
每次不斷地呼叫自己形成一個callback chain
終止點我們可以設計,當沒有傳遞參數的時候,即輸出結果。
因此:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const curryingSum = (arg) => { const fn = (x) => { if(x) { return curryingSum(arg + x); } else { return arg; } } return fn; }
const result = concatString('又舔')('又舔')('又')('又舔')('嘴唇'); console.log('result: ', result);
|
partial application
partial application又叫做偏函數
迷:可以翻譯一下嗎?
簡單來說就是,參數可以是各種形式,可接受多個以上。
有時候會用來作為處理過多重複的程式的中介
假設今天有以下的程式需要收斂:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const getSubTitle = (user, content, signs) => { return `${user}: ${content}${signs}` }
const speakShortSubTitle = () => { return getSubTitle('bucket', '又舔', '!!'); }
const speakMediumSubTitle = () => { return getSubTitle('bucket', '又舔又舔', '!!'); }
const speakFullSubTitle = () => { return getSubTitle('bucket', '又舔又舔又又舔嘴唇', '.'); }
|
初步可以收斂成:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| const getSubTitle = (user, content, signs) => { return `${user}: ${content}${signs}` }
const getBucketSubTitle = (content, signs) => { return getApiURL('bucket', content, signs) }
const speakShortSubTitle = () => { return getBucketSubTitle('又舔', '!!'); }
const speakMediumSubTitle = () => { return getBucketSubTitle('又舔又舔', '!!'); }
const speakFullSubTitle = () => { return getBucketSubTitle('又舔又舔又又舔嘴唇', '.'); }
|
不過可以看到還是有很多function重複使用getResourceURL
要讓它進一步的更好,可以試著導入partial application:
1 2 3 4 5
| const partial = (fn, ...argsToApply) => { return (...restArgsToApply) => { return fn(...argsToApply, ...restArgsToApply) } }
|
這邊使用closures的方式操作argsToApply,並且進一步的繼續持有特定的function。
我只處理一部份的事情,剩下的事情就交給別人了,這就是一種偏函數的操作方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const partial = (fn, ...argsToApply) => { return (...restArgsToApply) => { return fn(...argsToApply, ...restArgsToApply) } }
const getSubTitle = (user, content, signs) => { return `${user}: ${content}${signs}` }
const speakBucketSubTitle = partial(getBucketSubTitle, 'bucket');
const speakShortSubTitle = () => partial(speakBucketSubTitle, );
|
https://medium.com/dailyjs/functional-js-5-partial-application-currying-da30da4e0cc3
https://theanubhav.com/2019/02/03/js-currying-in-interview/