在JavaScript語言世界,函數是第一等公民。JavaScript函數是繼承自Function的對象,函數能作另一個函數的參數或者返回值使用,這便形成了我們常說的高階函數(或稱函數對象)。這就構成函數編程的第一要素。在JavaScript世界中有很多的函數式編程庫能輔助我們的JavaScript函數式體驗,在它們之中最為成功的要數Underscore或lodash。
如下lodash實例代碼:
var users = [
{ 'user': 'barney', 'age': 36 },
{ 'user': 'fred', 'age': 40 },
{ 'user': 'pebbles', 'age': 18 }
];
var names = _.chain(users)
.pluck('user')
.join(" , ")
.value();
console.log(names);
它以鏈式、惰性求值著稱,形成了一套自有的DSL風格。更多關於lodash的編程可以參見博主的另一篇文章JavaScript工具庫之Lodash。
函數式思想展現的是一種純粹的數學思維。函數並不代表任何物質(對象,相對於面向對象思想而言),而它僅僅代表一種針對數據的轉換行為。一個函數可以是原子的算法子(函數),也可以是多個原子算法子組成的組合算法子。它們是對行為的最高抽象,具有非凡的抽象能力和表現力。
雖然Underscore或lodash也提供了.compose(或.flowRight)函數來實現函數組合的能力,但ramdajs具有更強的組合力。
ramdajs是一個更具有函數式代表的JavaScript庫,可以在這里了解更多關於它的信息http://ramdajs.com/0.17/。它的這種能力主要來自它自有的兩大能力:自動柯里化和函數參數優先於數據。
自動柯里化
在計算機科學中,柯里化(Currying)是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,並且返回接受余下的參數且返回結果的新函數的技術。這個技術由 Christopher Strachey 以邏輯學家 Haskell Curry 命名的,盡管它是 Moses Schnfinkel 和 Gottlob Frege 發明的。
在理論計算機科學中,柯里化提供了在簡單的理論模型中比如只接受一個單一參數的lambda 演算中研究帶有多個參數的函數的方式。
ramdajs利用這一技術,默認所有API函數都支持自動柯里化。這為它提供了可以將另一個函數組合的先決條件。如常用的map操作需要接受兩個參數,在ramdajs中可以如下兩種方式實現:
R.map(function(item){
return item *2;
},
[2,3,5]
); //輸出[4, 6, 10]
var map = R.map(function(item){
return item *2;
});
map([2,3,5]); //輸出[4, 6, 10]
如果我們傳入2個完備的參數,則R.map函數將會直接執行。否則,它將返回另一個函數,等待參數完備時才執行。
關於JavaScript函數的柯里化,你還可以從博主的《JavaScript函數柯里化》中了解更多http://www.cnblogs.com/whitewolf/p/4495517.html
函數參數優先於數據
在UnderScore和lodash這類庫中,都要求首先傳入數據,然后才是轉換函數。而在ramdajs卻是顛覆性的改變。在它的規約中數據參數是最后一個參數,而轉換函數和配置參數則優於數據參數,排在前面。
將轉換函數放置在前面,再加上函數的自動柯里化,就可以在不觸及數據的情況下,將一個函數算法子包裝進另一個算法子中,實現兩個獨立轉換功能的組合。
假設,我們擁有如下兩個基礎算法子:
- R.multiply(a, b):實現 a *b; 2:R.map(func, data):實現集合 a –> b的map。
因為可以自動柯里化,所以有
R.multiply(10, 2); // 20
R.multiply(10) (2); // 20
所以上面對數組map的例子則可以轉為如下形式:
R.map(R.multiply(2)) ([2, 5, 10, 80]); // [4, 10, 20, 160]
R.map(R.multiply(2))的返回值也是一個函數,它是一個組合轉換函數。它組合了map和multiply行為。它利用R.map組合封裝了R.multiply(2)返回的柯里化函數,它等待map函數傳入對應的被乘數。
ramdajs的組合
有了上面的兩個條件,再加上ramdajs為我們提供的R.compose方法,我們就能很容易的實現更多算法子的組合。R.compose是從右向左執行的數據流向。
用ramdajs的組合來實現開篇lodash一樣的用戶名拼接的例子,則我們可以分為2個算法子的組合:
- R.pluck(prop):選擇對象固定屬性;
- R.join(data):對數組的字符串拼接。
則代碼如下所示:
var joinUserName = R.compose(R.join(" , "), R.pluck("user"));
joinUserName(users); // "barney , fred , pebbles"
這里的函數式組合可表示為下圖:
如果我們希望join用戶的年齡,則如下:
var joinUserAge = R.compose(R.join(" , "), R.pluck("age"));
joinUserAge(users); // "36 , 40 , 18"
假設我們希望輸出的不是用戶年齡,而是用戶生日,則我們可以輕易組合上一個減法的算法子:
- R.subtract(a, b):實現 a – b 數學算法。
則代碼如下:
var joinUserBrithDay = R.compose(R.join(","),R.map(R.subtract(new Date().getFullYear())),R.pluck("age"));
joinUserBrithDay(users); // "1979,1975,1997"
再如,我們希望獲取最年輕的用戶:
lodash實現:
_.chain(users)
.sortBy("age")
.first()
.value();
ramdajs則,可以組合獲取第一個元素的R.head算法子和排序算法子R.sortBy:
var youngestUser = R.compose(R.head, R.sortBy(R.prop("age")));
youngestUser(users); // Object {user: "pebbles", age: 18}
比如我們希望獲取年長的用戶,則只需再組合一個反序排列的算法子R.reverse:
var olderUser = R.compose(R.head, R.reverse, R.sortBy(R.prop("age")));
olderUser(users); // Object {user: "fred", age: 40}
希望你也能像我一樣喜歡上ramdajs,關於它的更多資料,請參見其官網 http://ramdajs.com/0.17/。