第一章 什么是函数式编程
一.前言
- 函数式编程:使用纯函数来构造程序。
(1)纯函数是没有副作用的函数。
(2)副作用
是指,函数执行过后,不仅有返回值,与此同时,还进行了其他操作:
(a) 修改了其他变量的值
(b) 设置了对象的属性
(c) 抛出了一个异常,或以一个错误终止
(d) 打印到终端,或获取用户键盘输入
(e) 读取或写入一个文件
- 纯函数的优势
(1)纯函数是模块化的,容易被测试,复用,并行化,泛华,推导。因此纯函数降低了bug出生的概率
(2)纯函数限制了怎样写程序,而非限制表达什么样的程序。就是说,纯函数依然能进行IO操作,循环改变变量等
二. 函数式程序对副作用的改造
- 第一个程序
class Cafe{
def buyCoffee(cc:CreditCard) : Coffee = {
val cup = new Coffee()
cc.charge(cup.price) // 这段代码出现副作用:
cup
}
}
```
(1)函数只是为了返回一个Coffee对象,而在此过程中,却涉及到了CreditCard对象的更改。因此产生副作用,这个副作用使得代码难以测试,因为信用卡计费更改操作可能是一个webservice,而我们在测试中并不真的想联系信用卡公司
(2)因此,我们传递一个Payments对象给函数,使代码更加模块化
2. 第2个程序
```
class Cafe{
def buyCoffee(cc:CreditCard,p:Payments) : Coffee = {
val cup = new Coffee()
p.charge(cc,cup.price) // 这段代码出现副作用:
cup
}
}
```
(1)改进后的优点:虽然payments的实现可能还是需要调用远程webservice,但是测试代码时,可以使用mock创造一个不用远程调用的接口
(2)仍然存在的缺点:如果想买12杯咖啡,需要对函数循环12次,这样会进入12次计费计算过程,会导致效率急剧下降。;
3. 函数式的代码写法
```
class Cafe{
def buyCoffee(cc:CreditCard) : (Coffee,Charge) = {
val cup = new Coffee()
// p.charge(cc,cup.price) 删除这段
(cup,Charge(cc,cup.price))
}
def buyCoffees(cc:CreditCard,n:Int):(List[Coffee],Charge) = {
val purchases:List[(Coffee,Charge)] = List.fill(n)(buyCoffee(cc))
val (coffees,charges) = purchases.unzip(coffees,charges.reduce( (c1,c2)=>c1.add(c2) ))
}
}
case class Charge(cc:CreditCard,amount:Double){
def add(other:Charge):Charge = { // 累计计费,只能累计一个卡
if(cc==other.cc)
Charge(cc,amount+other.amount)
else
throw new Exception("can't add different cards")
}
}
```
【注】:函数式程序的标准写法:纯内核 + 很薄的外壳
####三. 纯函数究竟是什么
1. `引用透明`:所谓引用透明的概念,就是指一个表达式,这个表达式在程序中可以被他的结果所取代,而不改变程序的含义
2. `纯函数`:我们利用引用透明的概念定义了纯函数:
假设存在一个函数$f$和一个引用透明的表达式$x$,若表达式$f(x)$对所有引用透明的表达式$x$也是引用透明的,那么$f$是一个纯函数
3. 非纯函数,往往会破坏`引用透明`,使同一个表达式产生的结果并不相同,使人费解
(eg:1+1表达式永远为2,这就是引用透明,但有些java函数,并不是引用透明的,例如stringbuilder的相关方法)
```scala
scala> val x = new StringBuilder("Hello")
x: StringBuilder = Hello
scala> val r1 = x.append(" world")
r1: StringBuilder = Hello world
scala> val r2 = x.append(" world") // r2和r1的表达式一样,但是得到的结果却不同
r2: StringBuilder = Hello world world
```