最近有看到一些柯裡化的文章,怎麼說呢,感覺很奇怪。一篇是阿裡雲的譯文,文章末尾給出了這樣一個 “curry”:
function curry(fn, ...args) {
return (..._arg) => {
return fn(...args, ..._arg);
}
}
作者前面明明例舉了柯裡化和部分應用的差別,結果最後說我們實作下柯裡化吧,然後寫了個部分應用……太假了,我忍不住評論了一下:
然後今天看到我們組歡哥的文章,說實話看了一下開頭這段代碼我就不太有耐心看下面具體的分析了:
// 定義占位符
var _ = '_';
function magician3 (targetfn, ...preset) {
var numOfArgs = targetfn.length;
var nextPos = 0; // 下一個有效輸入位置的索引,可以是'_',也可以是preset的結尾
// 檢視是否有足夠的有效參數
if (preset.filter(arg=> arg !== _).length === numOfArgs) {
return targetfn.apply(null, preset);
} else {
// 傳回'helper'函數
return function (...added) {
// 循環并将added參數添加到preset參數
while(added.length > 0) {
var a = added.shift();
// 擷取下一個占位符的位置,可以是'_'也可以是preset的末尾
while (preset[nextPos] !== _ && nextPos < preset.length) {
nextPos++
}
// 更新preset
preset[nextPos] = a;
nextPos++;
}
// 綁定更新後的preset
return magician3.call(null, targetfn, ...preset);
}
}
}
這是在幹嘛……然後歡哥他們發現了這段代碼有 bug,分析了一通,解決了 bug,美好的青春啊朋友們,出去喝酒蹦迪大保健不好麼,非得這麼揮霍生命麼……
在我們自己實作之前,對柯裡化沒什麼概念的同學可以看下 wiki(要看英文 wiki,中文 wiki 對柯裡化的解釋寫得又亂又不準确,容易和部分應用混淆),簡單來說柯裡化就是把一個多參函數轉換成接受單參的一系列函數。它跟部分應用的概念不太一樣,部分應用是把一個多參函數“切”一刀,而柯裡化是把函數“切”好多刀,直到中間每個函數都是單參的,最後得到的結果就是所謂的柯裡化函數(curried function)。在 JS 裡要手寫個 curried function 其實就是手寫個高階函數,沒什麼特别的。那要實作一個通用的 curry,該怎麼做呢,我不是針對誰,我是說上面那兩個實作都在賣萌……
const curry = (fn) => {
if (fn.length <= 1) return fn;
// 這是我一開始的實作
// 後來發現 rest 是多餘的,下面這樣就行了,fn.length 多處用到,可以提出來
// const generator = (args) => (args.length === fn.length ? fn(...args) : arg => generator([...args, arg]));
const generator = (args, rest) => (!rest ? fn(...args) : arg => generator([...args, arg], rest - 1));
return generator([], fn.length);
};
這不就三行代碼搞定了麼(不算函數聲明),測一下:
const sum = (a, b, c) => a + b + c;
const curriedSum = curry(sum);
const res = curriedSum(1)(2)(3)
console.log(res); // 6
const log = (a, b, c) => {
console.log(a, b, c);
};
const curriedLog = curry(log);
curriedLog('a')('b')('c'); // a b c
好像沒啥問題吧……emmmmm……歡迎打臉。
由此可見,在折騰什麼 curry 什麼 partial application 之前,還是多琢磨琢磨遞歸這種基本概念,順便附送一個 FP Style 的快排吧,突然感覺我也挺揮霍青春的……
const quickSort = (list) => {
if (!list || !list.length) return [];
if (list.length === 1) return list;
const [middle, ...rest] = list;
const reducer = (acc, x) => (
x <= middle ?
{ ...acc, left: [...acc.left, x] } :
{ ...acc, right: [...acc.right, x] }
);
const { left, right } = rest.reduce(reducer, { left: [], right: [] });
return [...quickSort(left), middle, ...quickSort(right)];
};
const list = [2, 3, 1, 8, 8, 1, 2, 18, 6, 2333];
const sorted = quickSort(list); // [ 1, 1, 2, 2, 3, 6, 8, 8, 18, 2333 ]
PS:評論裡有位大佬給了一個一行的實作
const curry = (fn, arr = []) => (...args) => (
arg => arg.length === fn.length
? fn(...arg)
: curry(fn, arg)
)([...arr, ...args])
我也隻能開動小腦筋把我的實作改成了一行……
// 跟三行的思路是一樣的,就是強行寫到了一行……
const curry = fn =>
(arg, args = [arg], rest = fn.length - 1) =>
(rest < 1 ? fn(...args) : newArg => curry(fn)(newArg, [...args, newArg], rest - 1));
對比上面兩個實作,我們會發現我的實作……敗了,因為我既然有 args,這個 rest 就是多餘的,是以改成這樣:
// 感覺還是多拆幾行比較好……
const curry = fn =>
(arg, args = [arg]) =>
(!fn.length || args.length === fn.length ? fn(...args) : newArg => curry(fn)(newArg, [...args, newArg]));
最後,如果你跟我一樣都喜歡java,也在學習java的道路上奔跑,歡迎你添加 V X sweetbest130 每天都會分享java最新業内資料,共同交流學習,讓學習變(編)成(程)一種習慣!