一般的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

基本上這邊會有幾個問題:

  1. callback hell
    • 沒什麼人會想看到這個東西的
  2. 彈性不夠
    • 只能固定使用5次,多餘或少於就爆掉了

上面我們提到實作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;
}
}
// 其他寫法:
// const fn = curryingSum.bind(this, arg + x);
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/


Comment