# 伍.2.2 JavaScript的函数式编程探索

[上一篇](5.2.1)讲了函数式编程的理论知识，本篇探索一下JavaScript如何进行函数式编程。

## 01.Ramda，一款实用的 JavaScript 函数式编程库

工欲善其事必先利其器，JavaScript函数式编程怎能没有库这种利器！

[Ramda](https://ramda.cn/)是一个非常适合函数式编程的函数库。函数库类似的有[lodash](https://www.lodashjs.com/)。

lodash已经很成熟了，提供了足够好用的函数，那为什么还要Ramda呢？笔者认为：Ramda的理念更适合函数式编程。

Ramda的理念是：

> Function first,data last.

也即函数优先，数据靠后。体现在具体的用法区别上，就是lodash会把第一个参数当成要处理的数据，函数等放在第一个参数之后传进来。而Ramda会把要出来的数据放在最后一个参数，最后一个参数之前的参数，都是传入的函数。

```javascript
const list = [{a: 1}, {a: 2}, {a: 3}];

//lodash：数据在前
_.findIndex(list, (o)=> o.a== 2); //>> 0


//Ramda：数据在最末尾
R.findIndex(R.propEq('a', 2),list); //>> 1
```

### 将数据放在后面有什么好处呢？

好处是写的代码更精简，更方便阅读理解。这点结合Ramda的柯里化会感受更加明显。

## 02.Ramda的函数都是柯里化的

上面的代码第8行，可以写成：

```javascript
R.findIndex(R.propEq('a', 2))(list); //>> 1
```

也是可以的，这不就是柯里化形态吗。没错，而Ramda提供的所有函数都是已经柯里化的，这可以从源代码找到证据。看看最简单的[add函数](https://github.com/ramda/ramda/blob/master/source/add.js)的源代码：

```javascript
import _curry2 from './internal/_curry2';


/**
 * Adds two values.
 *
 * @func
 * @memberOf R
 * @since v0.1.0
 * @category Math
 * @sig Number -> Number -> Number
 * @param {Number} a
 * @param {Number} b
 * @return {Number}
 * @see R.subtract
 * @example
 *
 *      R.add(2, 3);       //=>  5
 *      R.add(7)(10);      //=> 17
 */
var add = _curry2(function add(a, b) {
  return Number(a) + Number(b);
});
export default add;
```

上面代码第1行就引入了`_curry2`这个内部函数，然后第21行调用`_curry2`将`add`函数柯里化。Ramda每个对外的函数，都这样处理过。因此Ramda对外提供的每个函数都是柯里化过的。

### 全部柯里化的目的是什么呢？

仍然是为了与前面讲述的“将数据放在末尾”结合使用，以便体现代码的简洁、可读性。

现在举例说明：求列表中a的值大于1的项，然后取各项值之和。

```javascript
const list = [{ a: 1 }, { a: 2 }, { a: 3 }];

//lodash
{
    //根据给定的列表求和
    let sum = data => _.reduce(data, (a, b) => a + b.a,0);
    //得到值>1的项组成的列表
    let getList = data => _.filter(data, (o) => o.a > 1);
    let total = _.flow(getList, sum)(list);
    console.log(total);//>> 5
}


//Ramda
{
    let sum = data => R.reduce((a,b)=>a+b.a,0,data);
    let getList = data => R.filter((o) => o.a > 1,data);
    let total = R.compose(sum,getList)(list);
    console.log(total);//>> 5
}
```

这样可能还看不出明显差别，别急，因为Ramda提供的函数都是柯里化过的，柯里化可以将多参数函数拆分成单参数函数，于是可以改成如下：

```javascript
    let sum = data => R.reduce((a,b)=>a+b.a,0)(data);
    let getList = data => R.filter((o) => o.a > 1)(data);
    let total = R.compose(sum,getList)(list);
    console.log(total);//>> 5
```

基本可以发现一个Ramda代码的特征：第1、2行代码，作为数据的参数`data`固定地出现在参数和最末尾位置。既然第1，2行代码都有传数据参数`data`，而第3行代码传数据参数`list`，重复这么多次没有必要，同样是将数据以参数传递，笔者直觉这种情况出现一次就够了（理论上，**代码里有重复的地方都可以优化，出现一次就够**）。

那第1、2行多余的参数传递可以优化掉！因为末尾都是数据，那可以十分方便地优化掉（拿走就行了，比将数据放在最开头的参数位置而言，要方便得多）。再因为Ramda提供的函数都是柯里化过的，柯里化可以[延迟运行](../../../id-1/3.-qi-ta-zhi-shi-dian/1.3.2#2-yan-chi-yun-hang)，也即可以暂时不用传数据进来，在真正需要运行函数的时候传数据进来就可以了。所以，代码优化如下：

```javascript
    let sum = R.reduce((a,b)=>a+b.a,0);//优化掉data
    let getList = R.filter((o) => o.a > 1);//优化掉data
    let total = R.compose(sum,getList)(list);//最后再统一传参data
    console.log(total);//>> 5
```

哇！上面代码中第1、2行原本需要传递的参数`data`不见了，效果却一样！居然可以没`data`什么事？！的确可以！很清爽的代码。至此，就引出了一种无参数的编程风格：**Pointfree**，放在[下一篇](5.2.3)单独介绍。

可以发现正是因为Ramda把数据放在参数的最后一个位置，同时每个函数都柯里化过，因此能够省略一些参数，代码才变得更简洁，更易读。

## 参考文献

{% hint style="info" %}
[The Philosophy of Ramda](https://fr.umio.us/the-philosophy-of-ramda/#header)
{% endhint %}
