Scala 函数式编程

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()自身而言,遍历的顺序是未定义的,不过,一般都是从左到右遍历。

 这本书绝不轻易放过每个知识点,全书包含有大量习题,要求你自己实现 Scala 标准库或者 Scalaz 的既有功能。所以,当你读完本书,做完习题后,虽然你的应用开发能力并不会直接提升,但你会体会到构建函数语言和框架时的难点和取舍,从而增进你的框架开发语言设计的能力。   ——ThoughtWorks Lead Consultant 杨博   这本书所讲授的,正是基于 Scala函数式编程基础。基于 Scheme、Haskell 等老牌函数语言的传统教材的问题在于,相关语言的语法和思维方式与读者现有的知识体系迥异,容易造成较为陡峭的入门门槛。此外,由于这些语言本身的实际应用机会不多,初学者也难以在实战获得宝贵的直觉和经验。而在 Scala 的帮助下,这本书并不要求你抛开现有的思维方式另起炉灶,它所做的更像是为你现有的思维方式添砖加瓦,从而令你如虎添翼。   ——Spark committer from Databricks 连城   尽管函数式编程在近十多年用得越来越多,但市面上介绍其高阶特性的书却并不多。这本书在这方面是个重要的补充,它不仅仅面向 Scala 程序员,同样面向用任何编程语言开发的程序员,只要你充满好奇心。   ——挖财网首席架构师 王宏江   “让你洞察计算的本质。”   ——Martin Odersky, Scala的作者   “Scala和Java8开发者的函数式编程指南!”   ——William E. Wheeler, TekSystems   “本书向你展示了提升Scala技能的方法和理念,它已超过‘更好的Java’。”   ——Fernando Dobladez, Code54   “里面的练习有些挑战,很有趣,对你在真实世界使用它很有益。”   ——Chris Nauroth, Hortonworks   “边干边学,而非只是阅读。”   ——Douglas Alan、Eli和Edythe L. Broad,哈佛和麻省理工学院
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰茶不冰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值