函数式高级编程
1.1 偏函数
被大括号包含起来的一组case
语句就是一个偏函数.
偏函数并非对所有的输入值都有定义.
创建偏函数
object ParDemo {
def main(args: Array[String]): Unit = {
val f1 = new PartialFunction[Any, Int] {
// 接受传入的参数, 如果返回值值是true, 则会调用apply来创建对象
override def isDefinedAt(x: Any): Boolean = x.isInstanceOf[Int]
//
override def apply(v1: Any): Int = v1.asInstanceOf[Int] + 1
}
val list = List(1, 2, 3, 4, "abc")
val list2: List[Int] = list.collect(f1)
println(list2)
}
}
说明:
- 创建偏函数需要重写两个方法:
isDefinedAt
和apply
- 接受传入的参数, 如果返回值值是
true
, 则会调用apply
来创建对象, 如果false
, 则不会调用apply
方法, 相当于忽略了该元素. - 构建偏函数时,参数形式
[Any, Int]
是泛型,第一个表示参数类型,第二个表示返回参数 - 当使用偏函数时,会遍历集合的所有元素,编译器执行流程时先执行
isDefinedAt()
如果为true
,就会执行apply
, 构建一个新的Int
对象返回 执行isDefinedAt()
为false
就过滤掉这个元素,即不构建新的Int
对象. map
函数不支持偏函数,因为map
底层的机制就是所有循环遍历,无法过滤处理原来集合的元素collect
函数支持偏函数. 可以看成是支持偏函数的map
创建偏函数的简写方式
object ParDemo1 {
def main(args: Array[String]): Unit = {
val list = List(1, 2, 3, 4, "abc")
// 简写方式1: 花括号内直接写case语句, 就是一个偏函数
val f1: PartialFunction[Any, Int] = {
case x: Int => x + 1
}
println(list.collect(f1))
// 简写方式2:
val list2: List[Int] = list.collect({ case x: Int => x + 1 })
println(list2)
}
}
1.2 作为值的函数
在scala 中 函数为一级公民,函数也可以像数字一样存在变量中
object ValDemo1 {
def main(args: Array[String]): Unit = {
// fun存放 isOdd这个函数
// 后面的下划线表示: 我们确实是想用 fun 来存放函数, 而不是在调用这个函数
// 现在 fun和isOdd是完全等价的
val fun = isOdd _
println(fun(11))
println(isOdd(11))
}
def isOdd(x: Int): Boolean = x % 2 == 1
我们可以对函数做两件事情:
- 调用它
- 传递它:
- 把它存放在变量中
- 作为参数传递给另外一个函数
1.3 匿名函数
没有函数名的函数就是匿名函数.
一个匿名函数:
// 下面就是一个匿名函数: 接收一个 Double 类型的参数, 返回这个值的平方
(x: Double) => x * x
1.函数表达式
// 把匿名函数作为值存储在变量中
val square = (x: Double) => x * x
println(square(100))
2.作为参数传递
val list: List[Int] = List(10, 20, 30).map((x: Int) => x * x)
println(list)
3.作为值返回
object AnonymousDemo1 {
def main(args: Array[String]): Unit = {
val f1: Int => Int = foo()
println(f1(10))
println(foo()(1000))
}
def foo(): Int => Int = {
// 匿名函数作为值返回
x: Int => x * 2
}
1.4 作为参数的函数
object ParamDemo1 {
def main(args: Array[String]): Unit = {
import scala.math._
// 传入ceil函数 _ 表示在传递函数, 而不是调用函数
println(evaluate(ceil _))
println(evaluate(sqrt _))
println(evaluate(floor _))
// 传入一个匿名函数
print(evaluate(x => x + 10))
}
/*
接收一个函数作为参数.
内部是调用接收到的函数
*/
def evaluate(x: Double => Double): Double = {
x(0.25)
}
}
说明:
evaluate(x: Double => Double)
中的x: Double => Double
表示 x 的类型是一个函数: 这个函数只要一个Double
类型的参数, 返回值为为Double
类型的数据
1.5参数类型推断
object ParamDemo2 {
def main(args: Array[String]): Unit = {
// 完整的写法
val res1 = evaluate((n: Double) => n + 10)
// 因为evaluate接收的函数的参数类型必须是 Double, 所以可以省略n的类型
val res2 = evaluate((n) => n + 10)
// 因为匿名函数的参数只要一个, 所以圆括号可以省略. 注意: 其他情况都不能省略
val res3 = evaluate(n => n + 10)
// 如果参数在 => 右侧只出现一次, 就可以使用 _ 替换掉它. 这时左边的形参也可以省略了.
val res4 = evaluate(_ + 10) // 可读性更好: 表示一个把 形参 + 10 的函数, 并把值返回
println(res1 + "," + res2 + "," + res3 + "," + res4)
}
/*
接收一个函数作为参数.
内部是调用接收到的函数
*/
def evaluate(x: Double => Double): Double = {
x(0.25)
}
}
注意:
- 必须要有类型推断的情况下才能使用一些简写.
- Scala 的类型推断是根据所处的上下文来来完成的.
1.6高阶函数
能够接受函数作为参数或者把好函数作为返回值的函数就叫高阶函数.
我们上节课学习自定义的evaluate
函数, 还有以前学习过的map, filter, reduce
等都是高阶函数.
几个常用的高阶函数:
map
一帮用来从一个简单的集合中得到一个想要的集合.
filter
把一个集合中过滤掉不想要的集合
reduce
对一个集合中的所有元素进行归纳
// 计算 5 的阶乘
// _ * _ 表示一个二元函数(有两个参数的函数)
// 第一个 _ 表示第一个参数, 第二个 _ 表示第二个参数
println((1 to 5).reduce(_ * _))
foreach
和map
有点类似, 也是会遍历集合中的每个元素. 但是foreach
不会返回任何的值.
// 打印一个三角形
(1 to 9).map("*" * _).foreach(println _)
sortWith
object HighLevelDemo1 {
def main(args: Array[String]): Unit = {
// 返回一个按长度递减的数组
val arr1: Array[String] =
"I love playing Game"
.split(" ")
.sortWith(_.length > _.length)
println(arr1.toBuffer)
}
}
1.7闭包
闭包就是一个函数和与其相关的引用环境(变量)组合的一个整体(实体)
如果一个函数访问了外部函数的局部变量, 那么就可以把这个函数看成一个闭包.
// 这个函数就会返回一个闭包
def foo():()=>Int = {
var a = 10
() => a += 10; a // 返回值为这个闭包
}
闭包最佳实践
需求:
- 编写一个函数
makeSuffix(suffix: String)
可以接收一个文件后缀名(比如.jpg),并返回一个闭包 - 调用闭包,可以传入一个文件名,如果该文件名没有指定的后缀(比如.jpg) ,则返回 文件名.jpg , 如果已经有.jpg后缀,则返回原文件名。
- 要求使用闭包的方式完成
object ClosureDemo2 {
def main(args: Array[String]): Unit = {
val fun: String => String = makeSuffix("avi")
println(fun("hadoop视频"))
println(fun("scala视频"))
println(fun("苍老师视频.avi"))
}
def makeSuffix(suffix: String): String => String = {
fileName => if (fileName.endsWith(suffix)) fileName else fileName + "." + suffix
}
}
好处
- 返回的匿名函数和
makeSuffix (suffix string)
的suffix
变量 组合成一个闭包,因为 返回的函数引用到suffix
这个变量 - 我们体会一下闭包的好处,如果使用传统的方法,也可以轻松实现这个功能,但是传统方法需要每次都传入后缀名,比如
.avi
- 而闭包因为可以保留上次引用的某个值,所以我们传入一次就可以反复使用。大家可以仔细的体会一把!
1.8 函数的柯里化
简单来说 就是把之前需要两个参数的函数变成一个新的接收一个参数的函数
看下面的例子: 计算两个数的乘积
object CurryingDemo1 {
def main(args: Array[String]): Unit = {
// 如果想计算 6 * 7, 6 * 8, 6 * 9 ...
println(mul(6)(7))
println(mul(6)(8))
println(mul(6)(7))
}
// 正常写法
def normalMul(x: Int, y: Int) => x * y
// 函数柯里化
def mul(x: Int): Int => Int = {
y => x * y
}
}
简写的柯里化写法
object CurryingDemo1 {
def main(args: Array[String]): Unit = {
println(mul(3)(4))
println(mul(4)(5))
val foo: Int => Int = mul(6)
println(foo(7))
println(foo(8))
}
// 柯里化简写
def mul(x: Int)(y: Int): Int = x * y
}
1.9 控制抽象(Control Abstraction
)
在 Scala 给我们提供了为数不多的几种控制: if, for, while
等.
我们可以利用 Scala 强大的抽象能力, 可以自己实现控制结构.
看下面的高阶函数的代码:
package com.atguigu.day13
object ControlAbstract {
def main(args: Array[String]): Unit = {
// 定义一个可以在线程内执行传入的函数的函数
def runInThread(foo: () => Unit): Unit = {
new Thread() {
override def run(): Unit = foo()
}.start();
}
// 调用函数
runInThread(() => println("thread...."))
}
}
1.9.1 名调用表示法
由于 runInThread
的参数函数既没有参数又没有返回值, 所以可以使用名调用表示法:
在参数声明和函数调用的地方省略()
变成下面的样式:
// 参数部分省略 (), 但是 => 不可以省略
def runInThread(foo: => Unit) {
new Thread() {
override def run(): Unit = foo // foo的调用则不能再添加()
}.start();
}
// 调用函数的时候 ()=> 省略. 只写函数体即可
// 此处看起来更像一个代码块
runInThread {
println("thread....")
println("哈哈哈")
}
说明:
- 只有当参数函数为无参时候才可以这样省略.
- 看起来很爽:
runInThread
像编程语言中的关键字.
1.9.2 定义mywhile
"关键字"
我们利用前面的换名调用表示法和函数的柯里化来抽象出来一个类似系统的while
的关键字:mywhile
package com.atguigu.day13
object MyWhile {
def main(args: Array[String]): Unit = {
var a = 0
mywhile(a < 10) {
println(a)
a += 1
}
}
def mywhile(condition: => Boolean)(foo: => Unit): Unit = {
if (condition) {
foo
mywhile(condition) {
foo
}
}
}
}
1.10部分应用函数
部分应用函数和偏函数不是一个概念!
调用一个函数,实际上是在一些参数上应用这个函数。
如果传递了所有期望的参数,就是对这个函数的完整应用,就能得到这次应用或者调用的结果。
然而,如果传递的参数比所要求的参数少,就会得到另外一个函数。
这个函数被称为部分应用函数。 部分应用函数使绑 定部分参数并将剩下的参数留到以后填写变得很方便。
object OperatorDemo3 {
def main(args: Array[String]): Unit = {
// 只绑定第一个参数, 使用 _ 表示第二个参数未绑定
val f: Int => Int = foo(10, _: Int)
println(f(20))
println(f(30))
}
def foo(a: Int, b: Int): Int = {
a * b
}
}