Scala编程
函数式编程概述
Scala是一种命令式编程和函数式编程混合的编程语言,常见的面向对象编程就属于命令式编程,比如Java,C++等,常见的函数式编程语言有Lisp,Haskell,Erlang等。函数式编程最重要的基础是"λ演算",它是上世纪30年代数学家阿隆佐丘奇提出的,这是一套用于研究函数定义、函数应用和递归的形式系统,它被视为最小的通用程序设计语言,它的通用性体现在任何一个可计算的函数都能用一条变换规则和一个函数定义的方式来表达和求值,它强调的是变换规则的运用而不是实现它的具体机器,可以认为它是一个数理逻辑形式系统。
命令式语言是基于冯诺依曼计算机体系的,一个命令式程序给计算机提供命令序列让计算机原封不动的执行;而函数式编程(又称为泛函数编程)将计算机的计算看作数学上的函数计算,它避免了状态以及可变数据;
现在的计算机大多是多核CPU架构的,计算机可以同时运行多个线程(或者进程),对于命令式编程而言,由于多线程之间的状态共享,就需要引入相应机制进行并发控制(比如锁机制、消息传递机制);而函数式编程则不会在多个线程之间共享状态,不会造成资源竞争,也就不需要引入锁机制来保护可变状态,可以更好地实现并行处理。因此,函数式编程能更好地利用多核处理器的并行能力。
Scala是瑞士洛桑联邦理工学院的Martin Odersky教授在2001年设计开发的,它是一门类Java的多范式编程语言,它整合了面向对象编程和函数式编程。
函数式编程基础
在纯函数式编程中,变量是不可变的(你一定想问不可变还能叫变量嘛?),这里的变量和命令式编程语言中的变量的不同在于它已经确定就不能改变(就像Java类中使用final修饰的属性一样),如果要修改变量值只能将要修改的值赋值给一个新的变量;在函数式编程中函数和普通的值一样属于一等公民,可以像其他任何数据类型的值一样被传递和操作。
Scala提倡在上层架构上采用面向对象编程,而底层采用函数式编程;Scala不是完全的函数式编程,所以它不要求变量完全不可变,但它推荐采用函数式编程来实现具体的算法和操作数据结构;
函数式编程所需要的基础知识包含匿名函数、高阶函数、闭包、偏函数等。
函数的定义和使用
函数作为一等公民,那么它也应该像变量那样有类型和值的区别,类型需要明确函数接收多少参数、每个参数的类型、函数返回值的类型,值则是函数的具体实现;示例如下:
//最常见的函数定义(这是一种较为繁琐的定义方式,后面有比较简洁的方式介绍)
scala> val add:(Int,Int)=>Int = { (a,b)=> a+b}
例子中add是一个函数变量,add的类型是"(Int, Int) => Int",小括号中表示函数接收两个整型变量,"=>"右边的Int表示函数返回一个Int类型的值,而函数变量的值为"{(a,b)=> a+b}",如果函数变量的值只有一句,那么花括号可以省略,上面的定义方式的简化形式
scala>val add = (a:Int,b:Int) => a+b
Scala带有强大类型推导系统,"(a:Int,b:Int) => a+b"成为函数的字面量,系统可以根据它推断出函数的类型为"(Int, Int) => Int",这就是函数式编程的的匿名函数。
看过一些Scala的代码,可能看见一些类似这样的函数定义代码:val add = (_:Int)+(_:Int)
, 对于刚接触Scala的程序员这时可能就有点懵了;其实这只是Scala提供的语法糖而已,本质上这种函数定义方式和前面的匿名函数的方式是一样的;Scala中,当函数的每个参数在函数字面量中"=>"前后均只出现过一次时,可以省略"=>"并用下划线作为参数的占位符(第一个下划线表示第一个参数,第二个下划线表示第二个参数,以此类推),例如:
val count = (_:Int)+1 //当函数接收的参数有类型时不能省略小括号
val count = (x:Int) => x +1 //与上面一行的定义等效
val add = (_:Int)+(_:Int)
val add = (a:Int,b:Int) => a+b //与上一行等效
val list = List(1,2,3,4,5,6,7)
val doub = list.map(_*2) //map接收一个函数为参数,这里等效于list.map(x=>x*2),这里的下划线没用小括号是因为类型推断系统可以根据list推断出它是什么类型
方法和函数的区别
Scala中的方法和Java中的方法一样是类的一部分,方法有名字、类型签名、参数列表等,而Scala中的函数是一个完整的对象;
具体区别在于:
- 方法不能作为单独的表达式而存在(参数为空的方法除外),而函数可以
- 函数必须有参数列表,而方法可以没有
- 在需要函数的地方如果传递了一个方法,会自动进行ETA展开(也就是将方法转换为函数)
//直接将一个方法赋值给变量会报错
scala>def m(x:Int):Int = x+1
scala>val f = m //报错
scala>val f2:(Int)=>Int = m //进行了ETA展开
scala val f3 = m _ //scala提供的语法糖,将方法强制转换为函数,注意下划线与方法名之间有空格
高阶函数
前面已经提到,函数作为一等公民,是可以作为参数被传递和操作的,因此它也可以作为其他函数的参数或者返回值;当一个函数包含其他函数作为其参数或者返回结果为一个函数,此函数就称之为高阶函数;这就像高中数学里g(x)=2*x, f(x) = 2g(x) + 3,此处g(x)这个函数作为了一个参数传入了f()这个函数中,f()就是一个高阶函数。
说完高阶函数的定义,下面来展示一下高阶函数的高端操作,假设有一个场景需要分别计算一个整数到另外一个整数的“连加和”、“平方和”、“2的幂次和”,当不采用高阶函数时,常规的实现可以是:
//用普通的方法来实现
def pow(x:Int):Int = {if (x==0) 1 else 2 * pow(x-1)}
def sum(a:Int,b:Int): Int = {
if (a>b) 0 else a + sum(a+1,b)
}
def sumSquare(a:Int,b:Int):Int = {
if (a>b) 0 else a*a + sumSquare(a+1,b)
}
def sumPowOfTwo(a:Int,b:Int):Int = {
if (a>b) 0 else pow(a) + sumPowOfTwo(a+1,b)
}
可以看出上面的三个方法实现的逻辑几乎是相同的,唯一的区别的堆每个元素的处理逻辑不一样;这种情况下可以将处理逻辑抽象成一个函数,并将这个函数作为一个高阶函数sum的参数传进去;
def sum(f:Int => Int,a:Int,b:Int) = {
if(a>b) 0 else f(a) + sum(f,a+1,b)
}
scala>sum(x=>x,1,10) //与上面求连加和相同
scala>sum(x=>x*x,1,10) //平方和
scala>sum(pow,1,10) //此处进行了ETA展开
scala>sum(x=>x*x*x,1,10) //轻松实现立方和
怎么样?同样的需求,高阶函数的实现比常规的实现是不是更加优美且扩展性更强了了?