这篇文章上次修改于 268 天前,可能其部分内容已经发生变化,如有疑问可询问作者。

函数式编程的函数库

常见的函数库

1、Rxjs

Rxjs 从诞生以来一直都不温不火,但它函数响应式编程(Functional Reactive Programming,FRP)的理念非常先进,虽然或许对于大部分应用环境来说,外部输入事件并不是太频繁,并不需要引入一个如此庞大的 FRP 体系,但我们也可以了解一下它有哪些优秀的特性。

Rxjs 中,所有的外部输入(用户输入、网络请求等等)都被视作一种 『事件流』:用户点击了按钮 --> 网络请求成功 --> 用户键盘输入 --> 某个定时事件发生 —> 这种事件流特别适合处理游戏,上上下下上上下下举个最简单的例子,下面这段代码会监听点击事件,每 2 次点击事件产生一次事件响应:

var clicks = Rx.Observable
  .fromEvent(document, 'click')
  .bufferCount(2)
  .subscribe(x => console.log(x)); // 打印出前2次点击事件 

响应式编程是继承自函数式编程,声明式的,不可变的,没有副作用的是函数式编程的三大护法。其中不可变武功最高深。一直使用面向对象范式编程的我们,习惯了用变量存储和追踪程序的状态。RxJS从函数式编程范式中借鉴了很多东西,比如链式函数调用,惰性求值等等。

在函数中与函数作用域之外的一切事物有交互的就产生了副作用。比如读写文件,在控制台打印语句,修改页面元素的css等等。在RxJS中,把副作用问题推给了订阅者来解决。

2、Cycle.js

一个函数式和响应式的 JavaScript 框架,编写可观测代码。

Cycle.js 是一个基于 Rxjs 的框架,它是一个彻彻底底的 FRP 理念的框架,和 React 一样支持 virtual DOM、JSX 语法,但现在似乎还没有看到大型的应用经验。

本质的讲,它就是在 Rxjs 的基础上加入了对 virtual DOM、容器和组件的支持,比如下面就是一个简单的『开关』按钮:

function main(sources) {
  const sinks = {
    DOM: sources.DOM.select('input').events('click')
    .map(ev => ev.target.checked)
    .startWith(false)
    .map(toggled =>
      <div>
      <input type="checkbox" /> Toggle me
      <p>{toggled ? 'ON' : 'off'}</p>
      </div>
    )
  };
  return sinks;
}
const drivers = {
  DOM: makeDOMDriver('#app')
};

run(main, drivers);

3、Underscore.js

Underscore 是一个 JavaScript 工具库,它提供了一整套函数式编程的实用功能,但是没有扩展任何 JavaScript 内置对象。 他解决了这个问题:“如果我面对一个空白的 HTML 页面,并希望立即开始工作,我需要什么?” 他弥补了 jQuery 没有实现的功能,同时又是 Backbone 必不可少的部分。

Underscore 提供了100多个函数,包括常用的:map、filter、invoke— 当然还有更多专业的辅助函数,如:函数绑定、JavaScript 模板功能、创建快速索引、强类型相等测试等等。

4、Lodash.js

lodash是一个具有一致接口、模块化、高性能等特性的JavaScript工具库,是underscore.jsfork,其最初目标也是“一致的跨浏览器行为。。。,并改善性能”。

lodash采用延迟计算,意味着我们的链式方法在显式或者隐式的value()调用之前是不会执行的,因此lodash可以进行shortcut(捷径) fusion(融合)这样的优化,通过合并链式大大降低迭代的次数,从而大大提升其执行性能。

就如同jQuery在全部函数前加全局的$一样,lodash使用全局的_来提供对工具的快速访问。

var abc = function(a, b, c) {
  return [a, b, c];
};
var curried = _.curry(abc);
curried(1)(2)(3);

// lodash
function square(n) {
  return n * n;
}
var addSquare = _.flowRight(square, _.add);
addSquare(1, 2); 

5、Ramdajs

ramda是一个非常优秀的js工具库,跟同类比 更函数式主要体现在以下几个原则

1.ramda里面的提供的函数全部都是curry的 意味着函数没有默认参数可选参数从而减轻认知函数的难度。

2、ramda推崇pointfree简单的说是使用简单函数组合实现一个复杂功能,而不是单独写一个函数操作临时变量。

3、ramda有个非常好用的参数占位符 R._ 大大减轻了函数在pointfree过程中参数位置的问题相比underscore/lodash 感觉要干净很多。

应用场景

易调试、热部署、并发

1.函数式编程中的每个符号都是 const 的,于是没有什么函数会有副作用。谁也不能在运行时修改任何东西,也没有函数可以修改在它的作用域之外修改什么值给其他函数继续使用。这意味着决定函数执行结果的唯一因素就是它的返回值,而影响其返回值的唯一因素就是它的参数。

2.函数式编程不需要考虑”死锁"(deadlock),因为它不修改变量,所以根本不存在"锁"线程的问题。不必担心一个线程的数据,被另一个线程修改,所以可以很放心地把工作分摊到多个线程,部署"并发编程"(concurrency)。

3.函数式编程中所有状态就是传给函数的参数,而参数都是储存在栈上的。这一特性让软件的热部署变得十分简单。只要比较一下正在运行的代码以及新的代码获得一个diff,然后用这个diff更新现有的代码,新代码的热部署就完成了。

单元测试

  • 严格函数式编程的每一个符号都是对直接量或者表达式结果的引用,没有函数产生副作用。因为从未在某个地方修改过值,也没有函数修改过在其作用域之外的量并被其他函数使用(如类成员或全局变量)。这意味着函数求值的结果只是其返回值,而惟一影响其返回值的就是函数的参数。
  • 这是单元测试者的梦中仙境(wet dream)。对被测试程序中的每个函数,你只需在意其参数,而不必考虑函数调用顺序,不用谨慎地设置外部状态。所有要做的就是传递代表了边际情况的参数。如果程序中的每个函数都通过了单元测试,你就对这个软件的质量有了相当的自信。而命令式编程就不能这样乐观了,在 Java C++ 中只检查函数的返回值还不够——我们还必须验证这个函数可能修改了的外部状态。

总结与补充

  • 函数式编程是我们现有工具箱的一个很自然的补充—— 它带来了更高的可组合性,灵活性以及容错性。现代的JavaScript库已经开始尝试拥抱函数式编程的概念以获取这些优势。Redux 作为一种 FLUX的变种实现,核心理念也是状态机和函数式编程。
  • 函数式只是一种编程范式而已。很多实际应用中是很难用函数式去表达的,选择OOP 亦或是其它编程范式或许会更简单。但我们要注意到函数式编程的核心理念,如果说 OOP 降低复杂度是靠良好的封装、继承、多态以及接口定义的话,那么函数式编程就是通过纯函数以及它们的组合、柯里化、Functor 等技术来降低系统复杂度,而 React、Rxjs、Cycle.js 正是这种理念的代言。让我们一起拥抱函数式编程,打开你程序的大门!

总结

这篇文章是根据资料整理的,旨在了解函数式编程的一些库,还有其在实际中应用的意义和作用。

好了,对函数式编程的了解就到这了~~