如何学会函数式编程
本文是从 How to get started with functional programming 这篇文章翻译而来。
上周末,有人问我,如何学会函数式编程。我的回答是:用你现在使用的编程语言写纯正函数。
纯函数唯一的输入是它的参数,唯一的输出是它的返回值。如果你以前从未接触过这个概念,你会以为所有的函数都是纯正的。毕竟,所有的函数都是接受一个或多个输入值,返回一个输出值。但在某些传统编程中,经常会有一些外来的信息流入或流出函数。例如,一个不规范的函数有可能会依赖一个全局变量或一些类成员数据。在这种情况下,函数的行为并不完全决定于它的参数值。相似的,一个不规范的函数有可能会更改一个全局变量或修改数据库。这种情况下,函数除了返回值外,还会附带一些额外操作。
你可以用任何语言写出纯函数,只是有些语言容易写,有些语言写起来比较复杂。例如,没有人会把Fortran当作一种函数式语言,但有些人(M. J. D. Powell)却强制自己在Fortran里要写纯函数。
为什么要写纯函数?纯函数具有亲系透彻性(referential transparency),也就是说,针对相同的输入值,它一定给出相同的输出值。函数输出不依赖系统时间、数据库状态以及任何没有显式的作为参数传入函数的东西。这也表明纯函数易于理解(因此也易于调试和测试)。
你可以一直使用纯函数。但如果你想把一个值放到数据库里,光通过纯函数是实现不了的。或者当你想调用一个随机数发生器时,你可不想它保持亲系透彻性每次都返回相同的值。但是,在可以用到纯函数的时候,你应该使用纯函数,用纯函数来消除越界联系。完全的纯函数程序是不现实的;有人建议说最佳的纯度系数应该是 85% 。
那么,为什么程序员不大量的使用纯函数呢?一个原因是,纯函数需要更长的参数表。在面向对象的编程语言里,对象可以隐式的依赖对象状态来减少参数数量。对于这更简洁的方法接口,你付出的代价是,你无法只通过方法本身来理解这个方法。调用这个方法时你还需要知道对象的状态。为了获得更短的方法接口而放弃亲系透彻性值不值得?这依赖于你的上下文环境和你的风格,按我的观点,我更愿意用更长的函数接口来换取更纯的函数。
另外一个人们不太喜欢使用纯函数的原因是,把大型数据结构传入函数太麻烦。但这也依赖于你怎么干。你可以只是形式上的把一个对象传输函数,而不是把整个对象按字节拷贝进去。
为了效率,你也可以制造一些假纯度。例如,Mike Swaim最近在一个评论里给出了一个如何利用Memoization让程序的速度提升数个等级的例子。(Memoization是一种缓存技术。当一个函数向系统请求计算某些东西时,它首先看看这个东西是否已经被缓存过。如果是,它会从从缓存里取出结果返回。如果否,它会计算它,然后把输出放到缓存里。)
使用Memoization技术的函数,严格的说不是纯函数,它的计算操作直接受缓存状态的影响,但这样的函数仍然保持亲系透彻性,如果你给它相同的输入,它总会产生相同的输出。你可以认为称这样的函数为纯函数是一种欺骗,的确也是,但如果你总是纠结于这种事情,那你也知道,完全纯函数是有副作用的。