在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/。