第一步,理解函数式编程概念是最重要的一步,同时也是最难的一步。如果你从正确的角度或方法来理解的话,它也未必会有那么难。
回顾之前的部分: Part 1, Part 2, Part 3, Part 4, Part 5
现在该做什么?
现在你已经学会了所有这些新东西了,你可能在想,“现在该干什么?我如何在日常编程中使用它?”
这得看情况。如果你会使用纯函数式语言(如 Elm 或 Haskell)编程,那么你可以尝试所有这些想法。这些语言能够很容易实现这些想法。
如果你只会使用 Javascript 这样的命令式语言编程(我们中大多数人肯定都是),那么你仍然可以使用很多你学到的知识,但是还需要更多的训练。
Javascript 函数式
Javascript 有许多特性能让你以近乎函数式的方式编程。它不是纯粹的函数式,但你可以从语言中得到不变性,甚至更多的库。
它不是最佳的,但如果你必须使用的时候,那为什么不利用一些函数式语言的优点呢?
不变性
首先要考虑的是不变性。 在 ES2015,或者也叫 ES6,因为它有一个被称为 常量 的新关键字。这意味着一旦设置了变量,则无法修改该变量:
const a = 1;
a = 2; // this will throw a TypeError in Chrome, Firefox or Node
// but not in Safari (circa 10/2016)
这里的 a 被定义为常量,意味着一旦赋值无法再改变。 这就是为什么 a = 2 会抛出异常 (除了 Safari)。
Javascript 常量 有个问题就是不变性不够深入。以下示例说明了其限制:
const a = {
x: 1,
y: 2
};
a.x = 2; // NO EXCEPTION!
a = {}; // this will throw a TypeError
注意 a.x = 2 并没有抛出异常。 const 关键字的不变性只对变量 a 生效。 a 所指向的任何变量都可以改变。
这是非常令人失望的,因为它本可以让 Javascript 更好。
那么我们如何从 Javascript 中获得不变性呢?
很不幸,我们只能通过一个库 Immutable.js 来实现。 这可能给我们更好的不变性,但可悲的是,它实现的方式使我们的代码看起来更像 Java。
柯里化和组合
在本系列之前的文章,我们学习了如何编写柯里化的功能。这是一个更复杂的例子:
const f = a => b => c => d => a + b + c + d
请注意,我们不得不手工编写柯里化部分。
调用 f, 我们必须写成:
console.log(f(1)(2)(3)(4)); // prints 10
但是这么多的括号,足以让 Lisp 程序员哭泣了!(译者注:Lisp 语句中会使用很多括号)
有许多库能够简化这一过程。 我最喜欢的一个是 Ramda.
使用 Ramda 我们可以这样写:
const f = R.curry((a, b, c, d) => a + b + c + d);
console.log(f(1, 2, 3, 4)); // 打印 10
console.log(f(1, 2)(3, 4)); // 也打印 10
console.log(f(1)(2)(3, 4)); // 也打印 10
函数定义并没有什么改进,但我们已经消除了对所有括号的需求。请注意,我们可以应用与我们每次调用 f 时一样多的参数。
通过 Ramda, 我们可以重写 Part 3和 Part 4 mult5AfterAdd10 功能:
const add = R.curry((x, y) => x + y);
const mult5 = value => value * 5;
const mult5AfterAdd10 = R.compose(mult5, add(10));
事实证明,Ramda 有很多帮助函数来做这些事情,例如。R.add 和 R.multiply,这意味着我们可以少写代码:
const mult5AfterAdd10 = R.compose(R.multiply(5), R.add(10));
Map, Filter 和 Reduce
Ramda 也有它自己的 map, filter 和 reduce。 尽管这些功能在普通 Javascript Array.prototype 中已经存在, Ramda 的版本功能更加丰富:
const isOdd = R.flip(R.modulo)(2);
const onlyOdd = R.filter(isOdd);
const isEven = R.complement(isOdd);
const onlyEven = R.filter(isEven);
const numbers = [1, 2, 3, 4, 5, 6, 7, 8];
console.log(onlyEven(numbers)); // prints [2, 4, 6, 8]
console.log(onlyOdd(numbers)); // prints [1, 3, 5, 7]
R.modulo 用了两个参数. 第一个是 dividend (被除数) ,第二个参数是 divisor (除数)。
isOdd 函数只是除以 2 的余数。余数为 0 是 falsy, 不是奇数,余数为 1 则是 truthy,奇数。 我们翻转 modulo 的第一和第二参数,使得我们可以指定 2 作为除数。
isEven 功能只是 isOdd 的 complement(补集)。
onlyOdd 函数是通过 isOdd 来 断言(只返回布尔类型的方法) 的 过滤器 。它在等待 numbers 数组,即它在执行前需要的的最后一个参数。
The onlyEven 是一个使用 isEven 来断言的 过滤器 。
当我们将 numbers 传给 onlyEven 、onlyOdd 、isEven 和 isOdd 方法,获取它们最终的参数,最后执行然后返回我们期望的结果。
Javascript 缺点
Javascript 已经有很多的库,语言也得到增强,它仍然需要面对残酷的现实,它是一种命令式语言,对大家来说似乎能够做任何事情。
大多数前端人员在浏览器中一直使用着 Javascript ,因为一直以来只有这一种选择。但现在许多开发人员逐渐不再直接编写 Javascript。
取而代之,他们用不同的语言编写和编译,或者更准确的说,是用其他语言转换成 Javascript。
CoffeeScript 就是这些语言中的第一种。如今,Angular 2 中采用了 Typescript。Babel 也是一种 Javascript 转换编译器。
越来越多的人正在采用这种方法用于生产环境。
但是这些语言还是基于 Javascript ,而且只是稍微改进了一点点。为什么不从一个纯函数式语言转换到 Javascript?
Elm
在这个系列里,我们了解了 Elm 来帮助理解函数式编程。
但是什么才是 Elm?我又该怎么用它呢?
Elm 是一种纯函数式编程语言,最终编译成 Javascript ,所以你可以用它来创建 Web 应用,使用 The Elm Architecture,又叫 TEA(这个架构激励了 Redux 的开发者)。
Elm 程序没有任何运行时错误。
像 NoRedInk 这样的公司已经在生产环境中使用了 Elm,Elm 的创造者 Evan Czapliki 现在工作的公司(他之前在 Prezi 公司工作)。
看看这个访谈,6 个月应用 Elm 在生产环境, 由来自 NoRedInk 的 Richard Feldman 和 Elm 的布道者讲解。
我需要用 Elm 替换我所有的 Javascript 吗?
不,你可以逐渐替换。 完整的看看这篇文章 How to use Elm at Work,来学习更多知识。
为什么学习 Elm?
- 函数式编程是限制和*并存的。它限制了你可以做什么(大部分是保证你不会“误伤”自己),但是同时也让你远离 bug 和错误的设计决策,因为所有的 Elm 程序遵循 Elm Architecture,一个函数式响应编程模型。
- 函数式编程能让你成为一个更好的程序员。本文中的想法只是冰山一角。 你真的需要在实践中看到,它们是如何让你的程序缩小尺寸,增加稳定性。
- Javascript 最初是在 10 天内构建的,然后在过去的二十年中修补,以成为一种有点功能,有点面向对象和完全命令式的编程语言。 Elm 的设计吸取了 Haskell 社区过去 30 年工作中的知识,以及数十年的数学和计算机科学经验。 Elm 架构(TEA)是经过多年设计和完善的,是 Evan 在功能响应式性编程中论文的结果。看看 Controlling Time and Space(控制时间和空间),以了解这个设计的构思。
- Elm 专为前端 Web 开发人员而设计。 它的目的是使他们的工作更容易。 观看 Let’s Be Mainstream(让我们成为主流),更好地了解这一目标。
未来
不可能知道将来会怎样,但我们可以做一些猜测。下面是一些我的:
将会出现一个明确的语言,编译为 Javascript。
已经存在了 40 多年的函数式编程思想将被重新发现,以解决当前的软件复杂性问题。
硬件的状态,例如千兆字节的便宜内存和快速处理器,将使函数式技术成为可行。
CPU 不会变得更快,但内核的数量将继续增加。
可变状态将成为复杂系统中的最大问题之一。
我写这系列文章,因为我相信未来是函数式编程的未来,在过去的几年中,我在努力学习它(我还在学习)。
我的目标就是帮助别人比我更容易和更快的去学习这些概念,帮助别人成为更好的程序员,以便他们将来能有更好的就业前景。
即使我的预测,Elm 在未来将是一门伟大的语言是错误的,我可以肯定地说,函数式编程和 Elm 也会在未来的画卷上留下浓墨重彩的一笔。
我希望在阅读完本系列以后,你会对你的能力和这些概念的理解感到更加自信。
在今后的工作中,祝你好运。