七、Scala之函数式编程(高级部分)
Scala之函数式编程(高级部分)
文章目录
1. 偏函数
先看一个需求
给你一个集合val list = List(1, 2, 3, 4, "abc")
,请完成如下要求:
- 将集合list中的所有数字+1,并返回一个新的集合
- 要求忽略掉 非数字 的元素,即返回的 新的集合 形式为 (2, 3, 4, 5)
思路1-map+fliter方式
1 | val list = List(1, 2, 3, 4, "abc") |
思路2-模式匹配
1 | def addOne( i : Any ): Any = { |
可以看到,上面思路2的输出多了一个()
偏函数
- 在对符合某个条件,而不是所有情况进行逻辑操作时,使用偏函数是一个不错的选择
- 将包在大括号内的一组case语句封装为函数,我们称之为偏函数,它只对会作用于指定类型的参数或指定范围值的参数实施计算,超出范围的值会忽略(未必会忽略,这取决于你打算怎样处理)
- 偏函数在Scala中是一个特质(trait):PartialFunction
使用偏函数解决前面的问题
1 | val list = List(1, 2, 3, 4, "abc") |
偏函数的小结
- 使用构建特质的实现类(使用的方式是PartialFunction的匿名子类)
- PartialFunction 是个特质(看源码)
- 构建偏函数时,参数形式 [Any, Int]是泛型,第一个表示参数类型,第二个表示返回参数
- 当使用偏函数时,会遍历集合的所有元素,编译器执行流程时先执行
isDefinedAt()
如果为true ,就会执行 apply, 构建一个新的Int 对象返回 - 执行isDefinedAt() 为false 就过滤掉这个元素,即不构建新的Int对象.
- map函数不支持偏函数,因为map底层的机制就是所有循环遍历,无法过滤处理原来集合的元素
- collect函数支持偏函数
偏函数简化形式
声明偏函数,需要重写特质中的方法,有的时候会略显麻烦,而Scala其实提供了简单的方法
- 简化形式1
1 | def f2: PartialFunction[Any, Int] = { |
- 简化形式2
1 | val list3 = List(1, 2, 3, 4,"ABC").collect{ case i: Int => i + 1 } |
2. 作为参数的函数
函数作为一个变量传入到了另一个函数中,那么该作为参数的函数的类型是:function1,即:(参数类型) => 返回类型
1 | def plus(x: Int) = 3 + x |
map(plus(_))
中的plus(_)
就是将plus这个函数当做一个参数传给了map,_
这里代表从集合中遍历出来的一个元素,这个下换线和plus _
中的下换线含义是不一样的,plus _
中的_
是指:本身不执行这个方法,而是把这个方法的类型或引用拿出来。plus(_)
这里也可以写成 plus ,表示对 Array(1,2,3,4) 遍历,将每次遍历的元素传给plus的 x- 进行 3 + x 运算后,返回新的Int ,并加入到新的集合 result1中
def map[B, That](f: A => B)
的声明中的f: A => B
一个函数,A表示传入函数的参数类型,B表示传入函数的返回类型
3. 匿名函数
没有名字的函数就是匿名函数,可以通过函数表达式来设置匿名函数
1 | val triple = (x: Double) => 3 * x |
说明
1)(x: Double) => 3 * x
就是匿名函数。这样写也可以:
1 | def f(x: Double) = 3 * x |
(x: Double)
是形参列表, => 是规定语法表示后面是函数体,3 * x
就是函数体,如果有多行,可以 {} 换行写.- triple 是指向匿名函数的变量。
请编写一个匿名函数,可以返回2个整数的和,并输出该匿名函数的类型
1 | val f1 = (n1: Int, n2: Int ) => { |
4. 高阶函数
能够接受函数作为参数的函数,叫做高阶函数 (higher-order function)。可使应用程序更加健壮。
高阶函数基本使用
1 | //test 就是一个高阶函数,它可以接收f: Double => |
高阶函数可以返回函数类型
1 | //minusxy返回了一个匿名函数,该匿名函数是 (y: Int) => x - y |
高级函数案例的小结
说明: def minusxy(x: Int) = (y: Int) => x - y
- 函数名为 minusxy
- 该函数返回一个匿名函数
(y: Int) = > x -y
说明val result3 = minusxy(3)(5)
- minusxy(3)执行
minusxy(x: Int)
得到(y: Int) => 3 - y
这个匿名函数 - minusxy(3)(5)执行
(y: Int) => x - y
这个匿名函数 - 也可以分步执行:
val f1 = minusxy(3); val res = f1(90)
5. 参数(类型)推断
参数推断省去类型信息(在某些情况下[需要有应用场景],参数类型是可以推断出来的,如
1 | val list = List(1, 2, 3, 4) |
map中函数参数类型是可以推断的,同时也可以进行相应的简写。
- 参数类型是可以推断时,可以省略参数类型
- 当传入的函数,只有单个参数时,可以省去括号
- 如果变量只在
=>
右边只出现一次,可以用_
来代替
应用案例
1 | //分别说明 |
- map是一个高阶函数,因此也可以直接传入一个匿名函数,完成map
- 当遍历list时,参数类型是可以推断出来的,可以省略数据类型Int,即
list.map((x)=>x + 1)
- 当传入的函数,只有单个参数时,可以省去括号,即
list.map(x=>x + 1)
- 如果变量只在
=>
右边只出现一次,可以用_
来代替list.map(_ + 1)
再来看一个案例
1 | def f1(n1: Int, n2: Int): Int = { |
6. 闭包(closure)
基本介绍:闭包就是一个函数和与其相关的引用环境组合的一个整体(实体)
案例演示
1 | def minusxy(x: Int) = (y: Int) => x - y |
代码小结
- 第1点
(y: Int) => x – y
返回的是一个匿名函数 ,因为该函数引用到到函数外的 x,那么 该函数和x整体形成一个闭包
如:这里val f = minusxy(20)
的f函数就是闭包 - 你可以这样理解,返回函数是一个对象,而x就是该对象的一个字段,他们共同形成一个闭包
- 当多次调用f时(可以理解多次调用闭包),发现使用的是同一个x, 所以x不变。
- 在使用闭包时,主要搞清楚返回函数引用了函数外的哪些变量,因为他们会组合成一个整体(实体),形成一个闭包
闭包的最佳实践
请编写一个程序,具体要求如下
- 编写一个函数
makeSuffix(suffix: String)
可以接收一个文件后缀名(比如.jpg),并返回一个闭包 - 调用闭包,可以传入一个文件名,如果该文件名没有指定的后缀(比如
.jpg
) ,则返回文件名.jpg
, 如果已经有.jpg
后缀,则返回原文件名。 - 要求使用闭包的方式完成
4)提示:String.endsWith(xx)
1 | package myfunPack |
体会闭包的好处
1)返回的匿名函数和 makeSuffix (suffix string)
的 suffix 变量 组合成一个闭包,因为 返回的函数引用到suffix这个变量
2)我们体会一下闭包的好处,如果使用传统的方法,也可以轻松实现这个功能,但是传统方法需要每次都传入 后缀名,比如 .jpg ,而闭包因为可以保留上次引用的某个值,所以我们传入一次就可以反复使用。
7. 函数柯里化(curry)
-
函数编程中,接受多个参数的函数都可以转化为接受单个参数的函数,这个转化过程就叫柯里化
-
柯里化就是证明了函数只需要一个参数而已。其实我们刚才的学习过程中,已经涉及到了柯里化操作。
-
不用设立柯里化存在的意义这样的命题。柯里化就是以函数为主体这种思想发展的必然产生的结果。(即:柯里化是面向函数思想的必然产生结果)
函数柯里化快速入门
编写一个函数,接收两个整数,可以返回两个数的乘积,要求:
- 使用常规的方式完成
- 使用闭包的方式完成
- 使用函数柯里化完成
注意观察编程方式的变化。
1 | //说明 |
函数柯里化最佳实践
比较两个字符串在忽略大小写的情况下是否相等,注意,这里是两个任务:
- 全部转大写(或小写)
- 比较是否相等
针对这两个操作,我们用一个函数去处理的思想,其实也变成了两个函数处理的思想(柯里化)
方式1: 简单的方式,使用一个函数完成.
1 | def eq2(s1: String)(s2: String): Boolean = { |
方式2:使用稍微高级的用法(隐式类):形式为 str.方法()
1 | package myfunPack |
看一个上面的简写形式
1 | package myfunPack |
8. 控制抽象
看一个需求
如何实现将一段代码(从形式上看),作为参数传递给高阶函数,在高阶函数内部执行这段代码. 其使用的形式如 breakable{}
。
1 | package myfunPack |
控制抽象基本介绍
控制抽象是这样的函数,满足如下条件
- 参数是函数
- 函数参数没有输入值也没有返回值
1 | package myfunPack |
进阶用法:实现类似while的until函数
1 | var x=10 |
用抽象控制实现
1 | package myfunPack |