踏出理解函数式编程概念的第一步是最重要的一步,有时也是最难的一步。不过也不一定,取决于你们的思考方式。
既然你已经学会了所以的新东西,可能就会想了,“现在该做什么?如何将它用在我每天的编程中使用它?”
这要视情况而定。如果你能使用像 Elm 或 Haskell 这样的纯函数语言,你就可以尝试全部想法。而且在这些语言中实现起来很方便。
如果你只能使用像 JavaScript 这样的指令式语言,而且大多数肯定都是,仍然可以使用很多前面学到的知识,但将需要大量的规则。
JavaScript有很多特性让你用更函数式的方式编程。它不纯净,但可以通过一些库做到不可变数据甚至更多。
尽管不理想,但如果你不得不用它,为什么不使用一些函数式语言的优点呢。
首先思考一下数据不可变性。在ES2015(或者叫ES6)中,新增了关键字 const
。意味着一个变量赋值后,不能再被重新赋值:
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)(译者注: 在最新版的Safari中测试同样会抛异常)
JavaScript中的 const
的能力还远远不够。下面的例子阐述了这一限制:
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 而不是 JavaScript。
这系列的前几篇文章中,我们学习了如何编写柯里化的功能。下面这个更复杂的例子:
const f = a => b => c => d => a + b + c + d
注意,我们不得不手工写柯里化的部分。
而且在调用 f
时,不得不写:
console.log(f(1)(2)(3)(4)); // prints 10
但这些括号就足够让一个Lisp程序员哭了。
很多库能简化这个过程。我最喜欢的是 Ramda 。
我们可以用 Ramda 这么写:
const f = R.curry((a, b, c, d) => a + b + c + d); console.log(f(1, 2, 3, 4)); // prints 10 console.log(f(1, 2)(3, 4)); // also prints 10 console.log(f(1)(2)(3, 4)); // also prints 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));
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
需要两个参数。第一个是被除数(将要被除的)第二个是除数(用来除的)。
isOdd
只是一个除二取余的函数。余数是0表示假值,即不是奇数,1表示真值,是奇数。我们翻转了 modulo
的第一个和第二个参数,所以可以传入 2
作为除数。
isEven
函数只是 isOdd
的取反。
onlyOdd
是一个配合 isOdd
断言的过滤函数(返回布尔值的函数)。它接收包含数字的数组作为执行前的最后一个参数。
onlyEven
则是使用 isEven
进行断言的过滤函数。
当将数字传入 onlyEven
和 onlyOdd
时, isEven
和 isOdd
收到了他们最后的参数,最终会返回我们预期的数字。
即时现在的JavaScript有很多库,语言也有改善,但它仍然要面对自身是指令式语言的事实,把什么事都交给人来做。
大部分前端开发还在浏览器开发中使用JavaScript,因为很长一段时间里它是唯一的选择。但是现在很多开发者不再直接写JavaScript了。
取而代之,他们用另一种不同的语言编写代码,然后编译,更准确地说,是翻译到JavaScript。
CoffeeScript是第一个这类语言。并且现在,Angular 2已经结合了TypeScript。Babel也可以被认为是JavaScript的转换器。
越来越多的人在生产环境中使用这些技术。
但是这些语言也是基于JavaScript的,而且只是做了一点点改进。为什么不从一个纯函数语言转换到JavaScript呢?
在这系列文章中,我们已经使用Elm来帮助理解函数式编程了。
Elm是一个编译到JavaScript的纯函数式语言,所以你可以使用Elm构建Web应用,又叫TEA(这个架构给了Redux作者灵感)。
Elm程序中没有运行时错误。
像NoRedInk已经将Elm用于生产环境,Elm的创建者Evan Czapliki现在的公司(他之前在Prezi工作)。
看这个演讲, Elm在生产环境中的六个月 ,演讲者是来自NoRedInk的Richard Feldman和Elm的布道者。
不用,你可以逐渐地替换。看下这篇文章, How to use Elm at Work 了解更多。
未来怎么样我们无法知晓,但我们可以做一些合理的猜测。下面是我的猜想:
编译到JavaScript的语言将会明确。 沉淀了40年的函数式编程思想将再次被提出用来解决现在软件开发中的复杂问题。 硬件方面,比如千兆字节的便宜内存和快速处理器将带来函数式技术的可能。 CPU的速度不会更快了,但是核数还会继续增加。 可变状态将成为复杂系统中最大的问题。
我写下这系列文章,因为我相信函数式编程是未来,在过去几年中,我在一直努力地学习它(现在仍然在学)。
我的目标是让其他学习这些概念的人更好理解,帮助他们成为更好的开发者,让他们的职业生涯更好。
即使我语言ELm将成为一个伟大的语言是错的,但我能肯定地说无论未来怎样,函数式编程和Elm都具有历史性的意义。
希望阅读完这一系列文章,你能因自身能力和对这些概念的理解获得信心。
在今后的工作中,祝你好运。
本文根据 @Charles Scalfani 的《 So You Want to be a Functional Programmer (Part 6) 》所译,整个译文带有我们自己的理解与思想,如果译得不好或有不对之处还请同行朋友指点。如需转载此译文,需注明英文出处: https://medium.com/@cscalfani/so-you-want-to-be-a-functional-programmer-part-6-db502830403#.sms2qk304 。
前端开发,音乐,动漫,技术控,喜欢折腾新鲜事物,以不断学习的态度,在前端的路上走下去。