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) => BooleanFunction2特质拥有一个apply方法,在调用lessThan(10, 20)时实际上是对apply方法调用的语法糖:

lessThan.apply(10, 20) 		// true

这些类似Function2的特质,实际是由 Scala 标准库提供的普通特质,比如Function1Funciton3等等。其中的数字是指接收的参数个数。

因为这些函数在 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