Scala函数式编程
什么是函数式编程?
1、函数式编程将计算视为数学上的函数计算
2、函数成为了和普通的值一样的"头等公民",可以像任何其他数据类型的值一样被传递和操作
函数式编程成为越来越流行的编程范式
1、大数据应用和并发需求的驱动;
2、纯函数的行为表现出与上下文无关的透明性和无副作用性,避免了多线程并发应用中最复杂的状态同步问题。
Scala在架构层面上提倡上层采用面向对象编程,而底层采用函数式编程。
Scala并不是完全的函数式编程,不要求变量不可变,但他推荐尽量采用函数式来实现具体的算法和操作数据,多用val,少用var。
方法/函数的定义
方法
定义函数最通用的方法是作为某个类或者对象的成员,这种函数被称为方法,其定义的基本语法为:
def 方法名(参数列表):结果类型={方法体}
def关键字
{参数列表}:可选参数列表,列表中的每个参数都有一个名字,参数名后跟着冒号和参数类型。
结果类型:方法的返回值类型,可以是任意合法的Scala参数类型,若没有返回值,则返回类型为Unit
def add(a:Int,b:Int):Int={
var sum:Int=0
sum=a+b
return a+b //可以省略return,直接写成a+b
}
函数
如果将函数作为“头等公民”看待,函数应该有"类型"和"值"
类型需要明确函数接收多少个参数,每个参数的类型记忆函数返回结果的类型;
值则是函数的具体实现
定义:
val 匿名函数(函数字面量):函数变量的值
val counter:(Int)=>Int=(value=>value+1)
counter的类型是"(Int)=>Int",表示具有一个整数类型参数并返回一个整数的函数;
"{value=>value+1}“为函数字面量,作为counter的初始化值,”=>“前面的value是参数名,”=>"后面是具体的运算语句或表达式。
使用类型推断机制,可以省略函数类型
val counter=(value:Int)=>value+1
(value:Int)=>value+1
:字面量,也成为匿名函数
示例
占位符语法
当函数的每个参数在函数字面量内仅出现一次,可以省略=>并用下划线’_'作为参数的占位符来简化函数字面量的表示,第一个下划线表示第一个参数,第二个下划线代表第二个参数,依次类推。
有类型时括号不能省略,第一个等效于x:Int=>x+1
,第二个等效于(a:Int,b:Int)=>a+b
高阶函数
当一个函数包含其他函数作为其参数或者返回结果为一个函数时,该函数被称为高阶函数。
例:假设需要分别计算从一个整数到另一个整数的连加和、平方和以及2的幂次和。
方案一:不采用高阶函数。
//不使用高阶函数
def sumInts(a:Int,b:Int):Int={
if(a>b) 1 else a+sumInts(a+1,b)
}
def sumSquares(a:Int,b:Int):Int={
if(a>b) 0 else a*a+sumSquares(a+1,b)
}
def powerofTwo(x:Int):Int={
if(x==0) 1 else 2*powerofTwo(x-1)
}
def sumPowersOfTwo(a:Int,b:Int):Int={
if(a>b) 0 else powerofTwo(a)+sumPowersOfTwo(a+1,b)
}
println(sumInts(1,5))
println(sumSquares(1,5))
println(sumPowersOfTwo(1,5))
方案二:采用高阶函数
//使用高阶函数
def sum(f:Int=>Int,a:Int,b:Int):Int={
if(a>b) 0
else f(a)+sum(f,a+1,b)
}
def powerofTwo(x:Int):Int={
if(x==0) 1 else 2*powerofTwo(x-1)
}
println(sum(x=>x,1,5))
println(sum(x=>x*x,1,5))
println(sum(powerofTwo,1,5))
闭包
当函数的执行依赖于声明在函数外部的一个或多个变量时,则称这个函数称为闭包
闭包可以捕获闭包之外对自由变量的变化,反过来,闭包对捕获变量作出的改变在闭包之外也可见
对容器的操作
对于每种类型的容器,Scala都提供了一批类似的方法,实现类丰富的容器操作,只需要几个简单的函数调用就可以代替复杂的循环或者递归。更重要的是类库里的基本操作都是经过优化的。而且,容器类库已经支持在多核处理器上并行计算。常用的对容器的操作包括:遍历,映射,过滤,规约和拆分。
遍历操作
Scala容器的标准遍历方法foreach
函数原型为
def foreach[U](f: Elem=>U):Unit
该方法接收一个函数f作为参数;函数f的类型为Elem=>U
val list=List(1,2,3)
val f=(i:Int)=>println(i)
list.foreach(f)//调用函数
list.foreach(i=>println(i))//显式
list foreach(i=>println(i))//中缀标识法
list foreach println //最简式
val university=Map("XMU"->"Xianmen Uiversity",
"THU"->"Tsinghua University",
"PKU"->"Peking University"
)
university foreach(kv=>println(kv._1+":"+kv._2))
university foreach{x=>x match {
case(k,v)=>println(k+":"+v)
}}
university foreach{case(k,v)=>println(k+":"+v)}//简化
映射操作
映射是指通过对容器中的元素进行某些运算来生成一个新的容器。两个典型的映射操作就是map方法和flatMap方法.
map方法(一对一映射):将某个函数应用到集合中的每个元素,映射得到一个新的元素,map方法会返回一个与原容器类型大小都相同的新容器,只不过元素的类型可能不同。
flatMap方法(一对多映射):将某个函数应用到容器中的元素时,对每个元素都会返回一个容器,然后,flatMap把生成的多个容器"拍扁"成为一个容器并返回。返回的容器与原容器类型相同,但大小可能不同,其中元素的类型也可能不同。
过滤操作
过滤:遍历一个容器,从中获取满足指定条件的元素,返回一个新的容器。
filter方法:接受一个返回布尔值的函数f作为参数,并将f作用到每个元素上,将f返回真值的元素组成一个新容器返回。
filterNot方法过滤出不符合条件的元素;exists方法判断是否存在满足给定条件的元素;find方法返回第一个满足条件的元素。
规约操作
规约操作是对容器元素进行两两运算,将其“规约”为一个值。
reduce方法:接受一个二元函数f作为参数,首先将f作用在某个元素上并返回一个值,然后再将f作用在上一个返回值和容器的下一个元素上,再返回一个值,依此类推,最后容器中的所用值会被规约为一个值。
reduceLeft和reduceRight:前者从左到右进行遍历,后置从右到左进行遍历。
val list=List(1,2,3,4,5)
println(list.reduce(_+_))
fold方法:一个双参数列表的函数,从提供的初始值开始规约。第一个参数列表接受一个规约的初始值,第二个参数接受与reduce中一样的二元函数参数。
foldLeft和foldRight:前者从左到右进行遍历,后置从右到左进行遍历。
val list=List(1,2,3,4,5)
println(list.fold(10)(_*_))
拆分操作
拆分操作是把一个容器里的元素按一定的规则分割成多个子容器。常用的拆分方法有partition、groupedBy、grouped和sliding。
partition方法:接受一个布尔函数对容器元素进行遍历,以二元组的形式返回满足条件和不满足条件的两个集合。
val list=List(1,2,3,4,5)
println(list.partition(_%2==0))
groupedBy方法:接受一个返回U类型的函数对容器元素进行遍历,将返回值相同的元素作为一个子容器,并与该相同的值构成一个键值对,最后返回的是一个映射。
grouped和sliding方法:接受一个整型参数n,将容器拆分为多个与原容器类型相同的子容器,并返回由这些子容器构成的迭代器。其中,grouped按从左到右的方式将容器划分为多个大小为n的子容器(最后一个的大小可能小于n);sliding使用一个长度为n的滑动窗口,从左到右将容器截取为多个大小为n的子容器。