关于译者:这是一个流淌着沪江血液的纯粹工程:认真,是 HTML 最坚实的梁柱;分享,是 CSS 里最闪耀的一瞥;总结,是 JavaScript 中最严谨的逻辑。经过捶打磨练,成就了本书的中文版。本书包含了函数式编程之精髓,希望可以帮助大家在学习函数式编程的道路上走的更顺畅。比心。
译者团队(排名不分先后):阿希、blueken、brucecham、cfanlife、dail、kyoko-df、l3ve、lilins、LittlePineapple、MatildaJin、冬青、pobusama、Cherry、萝卜、vavd317、vivaxy、萌萌、zhouyao
如果您已经从头到尾通读了此书,请花一分钟的时间停下来回顾一下从第 1 章到现在的收获。相当漫长的一段旅程,不是吗?希望您已经收获了大量新知识,并用函数式的方式思考你的程序。
在本书即将完结时,我想给你提供一些关于使用官方函数式编程函数库的快速指南。注意这并不是一个详细的文档,而是将你在结束“轻量级函数式编程”后进军真正的函数式编程时应该注意的东西快速梳理一下。
如果有可能,我建议你不要做重新造轮子这样的事情。如果你找到了一个能满足你需求的函数式编程函数库,那么用它就对了。只有在你实在找不到合适的库来应对你面临的问题时,才应该使用本书提供的辅助实用函数 —— 或者自己造轮子。
在本书第 1 章曾列出了一个函数式编程库的列表,现在我们来扩展这个列表。我们不会涉及所有的库(它们之中有许多重复的内容),但下面这些你应该有所关注:
上面的列表只列出了所有函数式编程库的一小部分,并不是说没有在列表中列出的库就不好,也不是说列表中列出的就是最佳选择,总之这只是 JavaScript 函数式编程世界中的一瞥。您可以前往这里查看更完整的函数式编程资源。
Fantasy Land(又名 FL)是函数式编程世界中十分重要的学习资源之一,与其说它是一个库,不如说它是一本百科全书。
Fantasy Land 不是一份为初学者准备的轻量级读物,而是一个完整而详细的 JavaScript 函数式编程路线图。为了尽可能提升互通性,FL 已经成为 JavaScript 函数式编程库遵循的实际标准。
Fantasy Land 与“轻量级函数式编程”的概念相反,它以火力全开的姿态进军 JavaScript 的函数式编程世界。也就是说,当你的能力超越本书时,FL 将会成为你接下来前进的方向。我建议您将其保存在收藏夹中,并在您使用本书的概念进行至少 6 个月的实战练习之后再回来。
摘自 Ramda 文档:
Ramda 函数自动地被柯里化。 Ramda 函数的参数经过优化,更便于柯里化。需要被操作的数据往往放在最后提供。
我认为合理的设计是 Ramda 的优势之一。值得注意的是,Ramda 的柯里化形式(似乎大多数的库都是这种形式)是我们在第 3 章中讨论过的“松散柯里化”。
第 3 章的最后一个例子 —— 我们定义无值(point-free)工具函数 printIf()
—— 可以在 Ramda 中这样实现:
function output(msg) {
console.log( msg );
}
function isShortEnough(str) {
return str.length <= 5;
}
var isLongEnough = R.complement( isShortEnough );
var printIf = R.partial( R.flip( R.when ), [output] );
var msg1 = "Hello";
var msg2 = msg1 + " World";
printIf( isShortEnough, msg1 ); // Hello
printIf( isShortEnough, msg2 );
printIf( isLongEnough, msg1 );
printIf( isLongEnough, msg2 ); // Hello World
与我们在第 3 章中的实现相比有几处不同:
R.complement(..)
而不是 not(..)
在 isShortEnough(..)
周围新建一个否定函数 isLongEnough(..)
。R.flip(..)
而不是 reverseArgs(..)
函数,值得一提的是,R.flip(..)
仅交换头两个参数,而 reverseArgs(..)
会将所有参数反向。在这种情景下,flip(..)
更加方便,所以我们不再需要使用 partialRight(..)
或其他投机取巧的方式进行处理。R.partial(..)
所有的后续参数以单个数组的形式存在。R.uncurryN(..)
来获得一个包含所有参数的 printIf(..)
。如果我们这样做了,就相当于使用 R.uncurryN(2, ..)
包裹 R.partial(..)
进行调用,这是完全没有必要的。Ramda 是一个受欢迎的、功能强大的库。如果你想要在你的代码中实践 FP,从 Ramda 开始是个不错的选择。
Lodash 是整个 JS 生态系统中最受欢迎的库。Lodash 团队发布了一个“FP 友好”的 API 版本 —— "lodash/fp"。
在第 8 章中,我们讨论了合并独立列表操作(map(..)
、filter(..)
以及 reduce(..)
)。使用“lodash/fp”时,你可以这样做:
var sum = (x,y) => x + y;
var double = x => x * 2;
var isOdd = x => x % 2 == 1;
fp.compose( [
fp.reduce( sum )( 0 ),
fp.map( double ),
fp.filter( isOdd )
] )
( [1,2,3,4,5] ); // 18
与我们所熟知的 _.
命名空间前缀不同,“lodash/fp”将 fp.
定义为其命名空间前缀。我发现一个很有用的区别,就是 fp.
比 _.
更容易识别。
注意 fp.compose(..)
(在常规 lodash 版本中又名 _.flowRight(..)
)接受一个函数数组,而不是独立的函数作为参数。
lodash 拥有良好的稳定性、广泛的社区支持以及优秀的性能,是你探索 FP 世界时的坚实后盾。
在第 6 章中,我们已经快速浏览了一下 Immutable.js 库,该库可能是最广为人知的不可变数据结构库了。
让我们来看一下另一个流行的库:Mori。Mori 设计了一套与众不同(从表面上看更像函数式编程)的 API:它使用独立的函数而不直接在值上操作。
var state = mori.vector( 1, 2, 3, 4 );
var newState = mori.assoc(
mori.into( state, Array.from( {length: 39} ) ),
42,
"meaning of life"
);
state === newState; // false
mori.get( state, 2 ); // 3
mori.get( state, 42 ); // undefined
mori.get( newState, 2 ); // 3
mori.get( newState, 42 ); // "meaning of life"
mori.toJs( newState ).slice( 1, 3 ); // [2,3]
这是一个指出关于 Mori 的一些有趣的事情的例子:
vector
而不是 list
(你可能会想用的),主要是因为文档说它的行为更像 JavaScript 中的数组。mori.into(..)
,传入一个合适长度的数组来扩展 vector 的长度。在上例中,vector 有 43 个可用位置(4 + 39),所以我们可以在最后一个位置(索引为 42)上写入 "meaning of life"
这个值。mori.into(..)
创建一个较大的 vector,再用 mor.assoc(..)
根据这个 vector 创建另一个 vector 的做法听起来效率低下。但是,不可变数据结构的好处在于数据不会进行克隆,每次“改变”发生,新的数据结构只会追踪其与旧数据结构的不同之处。Mori 受到 ClojureScript 极大的启发。如果您有 ClojureScript 编程经验,那您应该对 Mori 的 API 感到非常熟悉。由于我没有这种编程经验,因此我感觉 Mori 中的方法名有点奇怪。
但相比于在数据上直接调用方法,我真的很喜欢调用独立方法这样的设计。Mori 还有一些自动返回原生 JavaScript 数组的方法,用起来非常方便。
JavaScript 不是作为函数式编程语言来特别设计的。不过其自身的确拥有很多对函数式编程非常友好基础语法(例如可作为变量的函数、闭包等)。本章提及的库将使你更方便的进行函数式编程。
有了本书中函数式编程概念的武装,相信你已经准备好开始处理现实世界的代码了。找一个优秀的函数式编程库来用,然后练习,练习,再练习。
就是这样了。我已经将我目前所知道的知识分享给你了。我在此正式认证您为“JavaScript 轻量级函数式编程”程序员!好了,是时候结束我们一起学习 FP 这部分的“章节”了,但我的学习之旅还将继续。我希望,你也是!