1、什麼是 curry
curry
就是咖哩一樣美好的工具性的拌料讓我們的函式更加的易用、低耦合性。
curry
的概念很簡單:只傳遞給函式一部分引數來呼叫它,讓它返回一個函式去處理剩下的引數。
你可以一次性地呼叫 curry
函式,也可以每次只傳一個引數分多次呼叫。
var add = function(x) {
return function(y) {
return x + y;
};
};
var increment = add(1);
var addTen = add(10);
increment(2);
// 3
addTen(2);
// 12
複製程式碼
curry 的重要性
在這個多彩的世界,有些事物對與我們來說並不是非必須的,就像我們早已習慣存在但是又非必須的東西:網際網路,移動手機,微波爐,電梯等等。當他們不存在的時候我們也能正常的快樂的生存下去,但是一旦擁有了以後他們的存在就變得不可或缺。就像我們的 curry 工具一樣。
- 我們來建立一個普通的 curry ,for your enjoyment 吧。這裡用到了 lodash 函式庫,不熟悉的朋友可以看一下 lodash 的官網
var curry = require('lodash').curry;
var match = curry(function(what, str) {
return str.match(what);
});
var replace = curry(function(what, replacement, str) {
return str.replace(what, replacement);
});
var filter = curry(function(f, ary) {
return ary.filter(f);
});
var map = curry(function(f, ary) {
return ary.map(f);
});
複製程式碼
我在上面的程式碼中遵循的是一種簡單,同時也非常重要的模式。即策略性地把要操作的資料(String, Array)放到最後一個引數裡。到使用它們的時候你就明白這樣做的原因是什麼了。
- 下面我們開始使用上面的程式碼,看看為什麼會這麼去處理我們的函式。
// 匹配空格
match(/\s+/g, "hello world");
// [ ' ' ]
match(/\s+/g)("hello world");
// [ ' ' ]
// 引出一個 hasSpace 的函式變數,暫存用
var hasSpaces = match(/\s+/g);
// function(x) { return x.match(/\s+/g) }
// 使用這個 hasSpace 去做一些相關的處理
hasSpaces("hello world");
// [ ' ' ]
hasSpaces("spaceless");
// null
// 現在我們知道了它的返回值,我們可以通過其他函式做進一步的處理。比如篩選出一個有空格的陣列值
filter(hasSpaces, ["tori_spelling", "tori amos"]);
// ["tori amos"]
// 儲存這個 findSpace 的函式變數
var findSpaces = filter(hasSpaces);
// function(xs) { return xs.filter(function(x) { return x.match(/\s+/g) }) }
// 輕鬆的使用吧
findSpaces(["tori_spelling", "tori amos"]);
// ["tori amos"]
var noVowels = replace(/[aeiou]/ig);
// function(replacement, x) { return x.replace(/[aeiou]/ig, replacement) }
var censored = noVowels("*");
// function(x) { return x.replace(/[aeiou]/ig, "*") }
censored("Chocolate Rain");
// 'Ch*c*l*t* R**n'
複製程式碼
我們為甚要這麼多繁瑣的步驟,而不是一步到位?
這裡表明的是一種“預載入”函式的能力,通過傳遞一到兩個引數呼叫函式,就能得到一個記住了這些引數的新函式。分解的使用的函式,讓每個函式更具有一定的獨立性,使用匯出的時候,做到純淨無汙染的傳遞。
擴充套件我們的 curry
curry
的用處非常廣泛,就像在 hasSpaces
、findSpaces
和 censored
看到的那樣,只需傳給函式一些引數,就能得到一個新函式。
- 下面我們用
map
包裹一下我們的函式
var getChildren = function(x) {
return x.childNodes;
};
var allTheChildren = map(getChildren);
複製程式碼
只傳給函式一部分引數這樣的操作通常被稱為區域性呼叫(partial application),因為只需要內聯呼叫能夠大量減少樣板檔案程式碼(boilerplate code)。
當我們談論純函式的時候,我們說它們接受一個輸入返回一個輸出。curry 函式所做的正是這樣:每傳遞一個引數呼叫函式,就返回一個新函式處理剩餘的引數。這就是一個輸入對應一個輸出啊。
練習一下
- 這裡引用了 ramda,如果沒有的話可以手動引入安裝一下和引用。
npm install ramda
var _ = require('ramda');
// 練習 1(區域性呼叫的使用)
//==============
// 通過區域性呼叫(partial apply)移除所有引數
var words = function(str) {
return split(' ', str);
};
// 練習 1a(組合使用函式)
//==============
// 使用 `map` 建立一個新的 `words` 函式,使之能夠操作字串陣列
var sentences = undefined;
// 練習 2
//==============
// 通過區域性呼叫(partial apply)移除所有引數
var filterQs = function(xs) {
return filter(function(x){ return match(/q/i, x); }, xs);
};
// 練習 3(柯里化~)
//==============
// 使用幫助函式 `_keepHighest` 重構 `max` 使之成為 curry 函式
// 無須改動:
var _keepHighest = function(x,y){ return x >= y ? x : y; };
// 重構這段程式碼:
var max = function(xs) {
return reduce(function(acc, x){
return _keepHighest(acc, x);
}, -Infinity, xs);
};
// use curry 1:
// ============
// 包裹陣列的 `slice` 函式使之成為 curry 函式
// //[1,2,3].slice(0, 2)
var slice = undefined;
// use curry 2:
// ============
// 藉助 `slice` 定義一個 `take` curry 函式,該函式呼叫後可以取出字串的前 n 個字元。
var take = undefined;
複製程式碼
這是上面的答案 Q&A,先別急著看答案,讓我們先思考一下
總結
通過簡單地傳遞幾個引數,就能動態建立實用的新函式;而且還能帶來一個額外好處,那就是保留了數學的函式定義,儘管引數不止一個。
下篇連結 瞭解 JavaScript 函數語言程式設計 - 程式碼組合的優勢