CH02-函数
高阶函数-把函数传递给函数
函数也是值,也可以赋值给一个变量、存储在一个数据结构里、像参数一样传递给另一个函数。
循环调用
使用循环方式实现阶乘:
def factirial(n:Int): Int = {
def go(n:Int, acc:Int):Int = { // 内部函数,跟一个函数内的变量含义相同
if (n <= 0) acc
else go(n-1, n * acc)
}
go(n, 1)
}
想不通过修改一个循环变量而实现循环功能,可以借助递归函数。这样的递归函数一般没命名为 go 或 loop。
上面的例子中,内部函数go
实现循环,接收下一个n
和和累积值acc
。要退出循环时,返回一个不继续递归的值,即n <= 0
。
编译器会检测到这种自递归,只要递归调用发生在尾部,即递归调用后面没有其他的调用,编译器会优化成类似while
循环的字节码。
尾调用
指调用者在一个递归调用之后不做其他事,只是返回这个调用结果。
比如
else go(n-1, n * acc)
部分,是直接返回了go
的递归调用,没有再做其他计算。如果是1 + go(n-1, n * acc)
,则不再是尾调用,因为又进行了计算。如果递归调用在尾部位置,则会自动编译为循环迭代,即不会每次进行栈的操作。可以通过
@annotation.tailrec
告诉编译器当没有成功编译成循环迭代时发出警告。
高阶函数
def formatResult(name:String, n:Int, f: Int => Int) = {
val msg = "The %s of %d is %d."
msg.format(name, n, f(n))
}
这里的参数f
,其类型为Int => Int
,表示接受一个整数并返回一个整数的函数。
因为高阶函数一般不能通过函数名来准确表示函数的功能,因此使用较短的函数命名,比如 g、h、f 等。
多态函数-基于类型的抽象
这里的多态跟继承中所说的“父类-子类继承”不同,这里是指类型的多态。
之前见到的函数都是单态的,因为函数只操作一种数据类型。适用于多种数据类型的函数,称为多态函数,或泛型函数。
多态函数的构建,一般是发现多个单态函数有相似的结构,这时,可以封装为一个多态函数。
实例
比如一个函数,返回数组中第一个匹配到 key 的索引,否则返回 -1:
def findFirst(ss:Array[String], key:String):Int = {
@annotatin.tailrec
def loop(n:Int):Int = {
if (n >= ss.length) -1 // 到达数组尾部仍未匹配到 key,返回 -1
else if (ss(n) == key) n // 匹配到 key,返回索引
else loop(n + 1) // 递归调用
}
}
这是从 String 数组中匹配,如果是从 Int 数组查找匹配,也是类似的结构,因此我们就可以将它改写为一个从 A 类型数组中查找对应索引的函数:
def findFirst[A](as:Array[A], p:A => Boolean):Int = {
@annotatin.tailrec
def loop(n:Int):Int = {
if (n >= as.length) -1
else if p(as(n)) n // 使用传入的 测试函数 p 对当前元素进行判断
else loop(n + 1)
}
}
函数名后跟的是类型参数,由中括号包围,多个参数使用逗号分隔,一般使用单个大写字母表示一个类型参数。这些类型参数作为类型变量,可以在其他类型签名中引用,比如上面的as
参数类型。
向高阶函数传入匿名函数
在调用高阶函数时,并没有必要提前定义一个有名函数然后再传入,可以在调用时直接定义一个函数值作为高阶函数的参数,这被称为匿名函数或函数字面量。比如:
findFirst(Array(1,2,3), (x:Int) => x == 9)
(x:Int) => x == 9
即为一个匿名函数,或称为函数字面量。
匿名函数中,=>
左边为该函数的参数列表,右边则会函数体。如果 Scala 可以推断参数的类型,则可以省略掉。
函数也是值
当定义一个函数字面量时,实际上是定义了一个包含一个
apply
方法的 Scala 对象。而当一个对象拥有apply
方法,则可以把该对象当做方法一样调用。比如我们定义一个函数字面量
(a, b) => a < b
,它事实上是创建函数对象的语法糖:val lessThan = new Function2[Int, Int, Boolean] { def apply(a:Int, b:Int) = a < b }
lessThan
的类型为Function2[Int, Int, Boolean]
,通常会写成(a, b) => Boolean
。Function2
特质拥有一个apply
方法,在调用lessThan(10, 20)
时实际上是对apply
方法调用的语法糖:lessThan.apply(10, 20) // true
这些类似
Function2
的特质,实际是由 Scala 标准库提供的普通特质,比如Function1
、Funciton3
等等。其中的数字是指接收的参数个数。因为这些函数在 Scala 中是普通对象,因此他们也是一等值。
通过类型实现多态
在实现多态函数时,各种可能的实现方式明显比普通函数减少了。比如针对类型 A 的多态函数,唯一能够对 A 进行操作的方式是传入一个函数作为参数,通过这个传入的函数来操作 A。
比如一个例子,这个函数签名表示它只有一种实现方式。它是一个执行部分应用的高阶函数。函数partial1
接收一个值和一个带有两个参数的函数,并返回一个带有一个参数的函数。
部分应用函数,表示函数被应用的参数并不是它需要的完整参数,即只提供了参数列表中的部分参数。
def partial1[A, B, C](a:A, f: (A, B) => c): B => C
我们该如何实现这个高阶函数呢。根据它的返回值类型,是接收一个类型 B 的参数并返回一个类型 C 的值的函数:
def partial1[A, B, C](a:A, f: (A, B) => c): B => C = {
(b: B) => ??? // 根据返回值类型 B => C ,定义一个该返回值类型的函数
}
现在如何来实现方法体部分呢,根据声明,这个函数必须返回一个 C 类型的值。而在partial1
的参数列表中,正好有一个函数f
能够返回一个 C 类型的值。除此之外,我们没有其他方式来实现该函数体。因此:
def partial1[A, B, C](a:A, f: (A, B) => c): B => C = {
(b: B) => f(a, b) // 调用参数中的函数,实现符合返回类型的函数体
}
这也就是部分应用函数的实现过程,一个函数,接收两个参数,返回一个值。当我们只提供一个参数值时,在这个例子中,是a
,这时会得到一个 “接收一个参数并返回一个值的” 函数。然后在提供原始函数需要的第二个参数,即b
,就能得到最终的结果, 即c
。
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.