1 引入
随着大数据时代的到来,函数式编程开始迅速崛起,因为,函数式编程可以较好满足分布式并行编程的需求(函数式编程一个重要特性就是值不可变性,这对于编写可扩展的并发程序而言可以带来巨大好处,因为它避免了对公共的可变状态进行同步访问控制的复杂问题)。
2 函数定义
2.1 函数字面量
在非函数式编程语言里,函数的定义包含了“函数类型”和“值”两种层面的内容。但是,在函数式编程中,函数是“头等公民”,可以像任何其他数据类型一样被传递和操作,也就是说,函数的使用方式和其他数据类型的使用方式完全一致了。这时,我们就可以像定义变量那样去定义一个函数,由此导致的结果是,函数也会和其他变量一样,开始有“值”。就像变量的“类型”和“值”是分开的两个概念一样,函数式编程中,函数的“类型”和“值”也成为两个分开的概念,函数的“值”,就是“函数字面量”。
大家比较熟悉的传统类型的函数,定义的语法和我们之前介绍过的定义“类中的方法”类似(实际上,定义函数最常用的方法是作为某个对象的成员,这种函数被称为方法):
def counter(value: Int): Int = { value += 1}
函数类型:
(Int) => Int
(当参数只有一个时,圆括号可以省略 Int => Int)
函数的值:
把函数定义中的类型声明部分去除,剩下的就是函数的“值” (value) => {value += 1}
(只有一条语句时,大括号可以省略)
现在我们再按照大家比较熟悉的定义变量的方式,采用Scala语法来定义一个函数:
val counter: Int => Int = { (value) => value += 1 }
可见,在Scala中,函数已经是“头等公民”,单独剥离出来了“值”的概念,一个函数“值”就是函数字面量。
2.2 匿名函数、Lambda表达式与闭包
我们不需要给每个函数命名,这时就可以使用匿名函数
:(num: Int) => num * 2
,这种匿名函数的定义形式,我们经常称为Lambda表达式
。
Lambda表达式
的形式如下:
(参数) => 表达式 //如果参数只有一个,参数的圆括号可以省略
我们可以直接把匿名函数存放到变量中
val myNumFunc: Int=>Int = (num: Int) => num * 2 // 把匿名函数定义为一个值,赋值给myNumFunc变量
实际上,Scala具有类型推断机制,可以自动推断变量类型,我们可以去掉myNumFunc的类型声明
,也就是去掉: Int=>Int
,或者省略num的类型声明
。但不能都省略
,因为,全部省略以后,实际上,解释器也无法推断出类型。
什么是闭包?
闭包是一个函数,一种比较特殊的函数,它反映了一个从开放到封闭的过程。
如何准确理解“从开放到封闭的过程”呢?
闭包与普通的函数不同,它会引用函数外部的变量。例如:
val addMore=(x:Int)=>x+more
more并没有在函数中定义,是一个函数外部的变量。如果这个时候去编译执行这条语句,编译器会报错,因为,more是一个自由变量,还没有绑定具体的值,因此,我们说这个时候这个函数是“开放的”。相对而言,这时的x就是一个绑定的变量,已经在函数中给出了明确的定义。因此,为了能够让addMore正常得到结果,不报错,必须在函数外部给出more的值,如下:
var more = 1
val addMore=(x:Int)=>x+more
addMore(10)
从上面代码可以看出,我们给more确定具体的值1以后,就让函数addMore中的more变量绑定了具体的值1,不再是“自由变量”,而是被绑定具体的值了,或者说“被关闭”了,这也是为什么我们说这样一个函数叫做“闭包”,它反映了一个从开放到封闭的过程。
什么是函数柯里化?
函数柯里化是指把一个参数列表的多个参数,变成多个参数列表。
先定义一个函数:
def add(x:Int,y:Int)=x+y // 这样调用 add(1,2)
现在我们把这个函数变一下形:
def add(x:Int)(y:Int) = x + y // 这样调用 add(1)(2)
实现过程
add(1)(2) 实际上是依次调用两个普通函数(非柯里化函数),第一次调用使用一个参数 x,返回一个函数类型的值,第二次使用参数y调用这个函数类型的值。
实质上最先演变成这样一个方法:
def add(x:Int)=(y:Int)=>x+y
那么这个函数是什么意思呢? 接收一个x为参数,返回一个匿名函数,该匿名函数的定义是:接收一个Int型参数y,函数体为x+y。现在我们来对这个方法进行调用。
val result = add(1)
返回一个result,那result的值应该是一个匿名函数:(y:Int)=>1+y
所以为了得到结果,我们继续调用result。
val sum = result(2)
最后打印出来的结果就是3。
2.3 高阶函数
一个接受其他函数作为参数或者返回一个函数的函数就是高阶函数。
可以用于实现从a到b的f(n)的累加形式(其中a<=n<=b):
def sum(f: Int => Int, a: Int, b: Int): Int = {
if(a > b) 0 else f(a) + sum(f, a+1, b)
}
def self(x: Int): Int = x
def square(x: Int): Int = x * x
def powerOfTwo(x: Int): Int = if(x == 0) 1 else 2 * powerOfTwo(x-1)
def sumInts(a: Int, b: Int): Int = sum(self, a, b)
def sumSquared(a: Int, b: Int): Int = sum(square, a, b)
def sumPowersOfTwo(a: Int, b: Int): Int = sum(powerOfTwo, a, b)
2.4 占位符语法
为了让函数字面量更加简洁,我们可以使用下划线作为一个或多个参数的占位符。
要求每个参数在函数字面量内仅出现一次
。
示例:
val numList = List(-3, -5, 1, 6, 9)
numList.filter(_ > 0) // 等价于numList.filter(x => x > 0 )
3 针对集合的操作
3.1 遍历操作
- 遍历List
val list = List(1, 2, 3, 4, 5)
for (elem <- list) println(elem)
//本行语句甚至可以简写为list.foreach(println),或者写成:list foreach println
list.foreach(elem => println(elem))
- 遍历 Map
val university = Map("XMU" -> "Xiamen University", "THU" -> "Tsinghua University","PKU"->"Peking University")
for ((k,v) <- university) printf("Code is : %s and name is: %s\n",k,v)
for (k<-university.keys) println(k)
for (v<-university.values) println(v)
university foreach {case(k,v) => println(k+":"+v)}
university.foreach({case (k,v) => println(k+":"+v)})
university.foreach(kv => println(kv._1+":"+kv._2))
3.2 map操作和flatMap操作
map操作是针对集合的典型变换操作,它将某个函数应用到集合中的每个元素
,并产生一个结果集合。
val books = List("Hadoop", "Hive", "HDFS")
books.map(s => s.toUpperCase)
// 返回结果 List[String] = List(HADOOP, HIVE, HDFS)
flatMap是map的一种扩展。在flatMap中,我们会传入一个函数,该函数对每个输入都会返回一个集合(而不是一个元素),然后,flatMap把生成的多个集合“拍扁”成为一个集合。
val books = List("Hadoop","Hive","HDFS")
books.flatMap(s => s.toList)
// 返回结果 List[Char] = List(H, a, o, o, p, H, i, v, e, H, D, F, S)
3.4 filter操作
filter可以实现遍历一个集合并从中获取满足指定条件的元素组成一个新的集合
val university = Map("XMU" -> "Xiamen University", "THU" -> "Tsinghua University","PKU"->"Peking University","XMUT"->"Xiamen University of Technology")
val universityOfXiamen = university filter {kv => kv._2 contains "Xiamen"} // 过滤得到那些学校名称中包含“Xiamen”的元素
3.5 reduce操作
reduce用于对集合中的元素进行归约,包含reduceLeft和reduceRight两种操作,前者从集合的头部开始操作,后者从集合的尾部开始操作。
reduce默认是reduceLeft。
val list = List(1,2,3,4,5)
list.reduceLeft(_ - _) // -13
list.reduceRight(_ - _) // 3
3.6 fold操作
和reduce(归约)操作比较类似。fold操作需要从一个初始的“种子”值开始,并以该值作为上下文,处理集合中的每个元素。
val list = List(1,2,3,4,5)
list.fold(10)(_*_) // 1200
fold有两个变体:foldLeft()和foldRight(),其中,foldLeft(),第一个参数为累计值,集合遍历的方向是从左到右。foldRight(),第二个参数为累计值,集合遍历的方向是从右到左。对于fold()自身而言,遍历的顺序是未定义的,不过,一般都是从左到右遍历。