前几天在Manning网买了本《Functional Programming in Scala》电子书,正好趁春节长假好好学习一下。
这不是一本关于Scala编程的书,而是介绍函数编程(Functional Programming,简称FP)概念和原理的书,用Scala作为示例,其原理可以用于任何一种函数编程语言,例如F#、Erlang、clojure等。而一些其他语言如Python、Ruby、Javascript等,对函数编程的支持都很强,一些函数编程原理也可以同样适用于这些语言。
第一章 什么是函数编程
"函数编程"是一种"编程范式"(programming paradigm),也就是如何编写程序的方法论。它基于这样一个简单的限制条件:只用“纯函数”编写程序,换句话说,就是只用没有“副作用”的函数编程。
通常情况下,以下一些操作都会带来副作用:
- 修改变量
- 修改数据结构
- 设置对象的属性
- 捕获异常
- 控制台输入或者输出
- 读写一个文件
- 往显示屏上输出
假设一个程序不能做以上任何事情,我们难以想象,这个程序还能有什么用处。如果不能修改变量,那么如何写循环语句呢?函数编程就是告诉我们如何写出不带副作用的程序。事实证明,增加这种限制是有很多好处的,我们编写的程序更加模块化,纯函数的程序更容易测试,重用,并行化。为了获得这些好处,我们必须转变编程方式。
一个函数有一个类型A的输入和B类型的输出(在Scala中表示为A => B),对于每个指定的类型A的值a,都有唯一一个对应的B类型的值b。
例如,intToString就是一个Int => String的函数,对于每个指定的Int类型的数,都输出一个String类型的值。除此之外,它不做任何事情。换句话说,就是没有可以察觉到的其他任何计算返回值以外的操作,我们称这个函数为“纯函数”。其他的一些纯函数比如“+”,对于任何给定的两个整数,每次执行都会返回相同的一个整数。Java或者scala中String类型的length也是如此,其他还有很多这样的纯函数。
纯函数可以用表达式取代另外一个表达式中的值,例如,x=3+5,那么y=x+1 和 y=(3+5)+1 是完全等价的。非纯函数就不是这样了,下面用两个例子说明它们之间的区别。
scala> val x = "Hello, World"
x: java.lang.String = Hello, World
scala> val r1 = x.reverse
r1: String = dlroW ,olleH
scala> val r2 = x.reverse
r2: String = dlroW ,olleH
scala> val r1 = "Hello, World".reverse
r1: String = dlroW ,olleH
val r2 = "Hello, World".reverse
r2: String = dlroW ,olleH
在这个例子中,reverse是一个纯函数。reverse不会有副作用。
scala> val x = new StringBuilder("Hello")
x: java.lang.StringBuilder = Hello
scala> val y = x.append(", World")
y: java.lang.StringBuilder = Hello, World
scala> val r1 = y.toString
r1: java.lang.String = Hello, World
scala> val r2 = y.toString
r2: java.lang.String = Hello, World
scala> val x = new StringBuilder("Hello")
x: java.lang.StringBuilder = Hello
scala> val r1 = x.append(", World").toString
r1: java.lang.String = Hello, World
scala> val r2 = x.append(", World").toString
r2: java.lang.String = Hello, World, World
而在这里例子中,append除了返回以外,还改变了x的值,带来了副作用,因此它不是一个纯函数。
增加了这样限制的函数编程,给我们带来了很大程度的模块化。模块化程序是可以理解的和可以重复使用的组件,它独立于整体。这样,整体的含义仅仅依赖于它的组件和它们之间组合的规则的含义。模块可以看成是一个黑盒子,使得逻辑计算具有可重用性。后面的章节都是讨论如何进行函数编程。