Skip to content

Latest commit

 

History

History
421 lines (370 loc) · 9.6 KB

第八章 函数和闭包.md

File metadata and controls

421 lines (370 loc) · 9.6 KB

第八章 函数和闭包

8.1 方法

import scala.io.Source

object 长行 {

  def 处理文件(文件名: String, 宽度: Int) = {
    val  = Source.fromFile(文件名)
    for (行 <- 源.getLines())
      处理行(文件名, 宽度, 行)
  }
  
  private def 处理行(文件名: String, 宽度: Int, : String) = {
    if (行.length > 宽度)
      println(文件名 + ": " + 行.trim)
  }
}

为在命令行使用长行:

object 搜索长行 {
  def main(参数: Array[String]) = {
    val 宽度 = 参数(0).toInt
    for (某参数 <- 参数.drop(1))
      长行.处理文件(某参数, 宽度)
  }
}

先编译: $ scalac 长行.scala 运行:

$ scala 搜索长行 45 长行.scala
长行.scala: private def 处理行(文件名: String, 宽度: Int, 行: String) = {

或者直接运行:

$ scala 长行.scala 45 长行.scala
长行.scala: private def 处理行(文件名: String, 宽度: Int, 行: String) = {

8.2 本地函数

处理行内置于处理文件:

  def 处理文件(文件名: String, 宽度: Int) = {
    def 处理行(文件名: String, 宽度: Int, : String) = {
      if (行.length > 宽度)
        println(文件名 + ": " + 行.trim)
    }
    
    val  = Source.fromFile(文件名)
    for (行 <- 源.getLines())
      处理行(文件名, 宽度, 行)
  }

可以直接使用外围函数的参数:

  def 处理文件(文件名: String, 宽度: Int) = {
    def 处理行(: String) = {
      if (行.length > 宽度)
        println(文件名 + ": " + 行.trim)
    }
    
    val  = Source.fromFile(文件名)
    for (行 <- 源.getLines())
      处理行(文件名, 宽度, 行)
  }

8.3 头等函数

函数除了调用, 还可以作为值进行传递, 如:

(x: Int) => x + 1
scala> var 递增 = (数: Int) => 数 + 1
递增: Int => Int = $$Lambda$1024/713464342@ae202c6

scala> 递增(10)
res0: Int = 11

可赋值递增:

scala> 递增 = (数: Int) => 数 + 9999
递增: Int => Int = $$Lambda$1081/1947060963@4e140497

scala> 递增(10)
res1: Int = 10009

如函数有多声明, {}包裹:

scala> 递增 = (数: Int) => {
     |   println("咱")
     |   println("在")
     |   println("这!")
     |   数 + 1
     | }
递增: Int => Int = $$Lambda$1084/1698322791@5a85b4e6

scala> 递增(10)
咱
在
这!
res2: Int = 11

很多库使用函数作为参数, 如foreach:

scala> val 几个数 = List(-11, -10, -5, 0, 5, 10)
几个数: List[Int] = List(-11, -10, -5, 0, 5, 10)

scala> 几个数.foreach((数: Int) => println(数))
-11
-10
-5
0
5
10

过滤:

scala> 几个数.filter((数: Int) => 数 > 0)
res4: List[Int] = List(5, 10)

8.4 简写函数

省去参数类型:

scala> 几个数.filter((数) => 数 > 0)
res5: List[Int] = List(5, 10)

省去括号:

scala> 几个数.filter(数 => 数 > 0)
res6: List[Int] = List(5, 10)

8.5 占位符语法

scala> 几个数.filter(_ > 0)
res8: List[Int] = List(5, 10)

编译器有时不能理解占位符所代表类型:

scala> val 函数 = _ + _
<console>:11: error: missing parameter type for expanded function ((x$1: <error>, x$2) => x$1.$plus(x$2))
       val 函数 = _ + _
                ^
<console>:11: error: missing parameter type for expanded function ((x$1: <error>, x$2: <error>) => x$1.$plus(x$2))
       val 函数 = _ + _
                    ^

可指定类型:

scala> val 函数 = (_: Int) + (_: Int)
函: (Int, Int) => Int = $$Lambda$1118/119282849@20cf3ab3

scala> 函数(5, 10)
res9: Int = 15

8.6 部分应用函数

几个数.foreach(println _)

等价于:

几个数.foreach(数 => println(数))

_可用于应用函数于某些参数

scala> def 求和(甲: Int, 乙: Int, 丙: Int) = 甲 + 乙 + 丙
求和: (甲: Int, 乙: Int, 丙: Int)Int

scala> 求和(1, 2, 3)
res13: Int = 6

部分应用函数就是在调用函数时不提供所有参数的表达式, 比如不提供参数:

scala> val a = 求和 _
a: (Int, Int, Int) => Int = $$Lambda$1142/899094347@24a38ef3

scala> a(1, 2, 3)
res14: Int = 6

等价于:

scala> a.apply(1, 2, 3)
res15: Int = 6

少提供一个参数:

scala> val b = 求和(1, _: Int, 3)
b: Int => Int = $$Lambda$1143/1555939899@c35b83c

scala> b(2)
res16: Int = 6

如果_代替的是所有参数, 可以省略, 比如:

几个数.foreach(println _)

等价于

几个数.foreach(println)

直接赋值函数如下会导致编译错误:

scala> val a = 求和
<console>:12: error: missing argument list for method 求和
Unapplied methods are only converted to functions when a function type is expected.
You can make this conversion explicit by writing `求和 _` or `求和(_,_,_)` instead of `求和`.
       val a = 求和
               ^

8.7 闭包

下面的是绑定变量, 而增量由于没有定义是自由变量:

scala> (数: Int) => 数 + 增量
<console>:12: error: not found: value 增量
       (数: Int) => 数 + 增量
                       ^

但只要有增量定义, 同样函数定义即合法:

scala> var 增量 = 1
增量: Int = 1

scala> val 累加 = (数: Int) => 数 + 增量
累加: Int => Int = $$Lambda$1146/681292833@8e164f2

scala> 累加(10)
res20: Int = 11

在运行时通过"捕捉"自由变量的绑定值来"闭合"函数就叫"闭包". 如果增量在闭包形成后改变, 闭包也随之改变:

scala> 增量 = 9999
增量: Int = 9999

scala> 累加(10)
res21: Int = 10009

同样如果变量在闭包中有修改, 外部也可见此修改:

scala> var 和 = 0
和: Int = 0

scala> 几个数.foreach(和 += _)

scala> 和
res24: Int = -11

在运行时, 闭包牵涉的变量值可能改变, 关键在于该闭包创建的时刻变量值如何:

scala> def 创建累加器(增量: Int) = (数: Int) => 数 + 增量
创建累加器: (增量: Int)Int => Int

scala> val 累加器1 = 创建累加器(1)
累加器1: Int => Int = $$Lambda$1168/1847678962@36f59005

scala> val 累加器9999 = 创建累加器(9999)
累加器9999: Int => Int = $$Lambda$1168/1847678962@4a83d668

scala> 累加器1(10)
res25: Int = 11

scala> 累加器9999(10)
res26: Int = 10009

8.8 特殊的函数调用方式

重复参数

scala> def 回音(参数: String*) =
     |   for (某参数 <- 参数) println(某参数)
回音: (参数: String*)Unit

scala> 回音()

scala> 回音("一")
一

scala> 回音("吃了", "吗!")
吃了
吗!

如果传入数组, 编译报错:

scala> val 数组 = Array("咋", "样", "啊?") 
数组: Array[String] = Array(咋, 样, 啊?)

scala> 回音(数组)
<console>:14: error: type mismatch;
 found   : Array[String]
 required: String
       回音(数组)
          ^

可以通过: _*达成:

scala> 回音(数组: _*)
咋
样
啊?

有名参数 调用时, 参数可以用与函数定义时的顺序不同:

scala> def 速度(距离: Float, 时间: Float): Float =
     |   距离 / 时间
速度: (距离: Float, 时间: Float)Float

scala> 速度(100, 10)
res32: Float = 10.0

scala> 速度(时间 = 10, 距离 = 100)
res33: Float = 10.0

默认参数值

调用时可以选择省去指定值

scala> def 打印时间(输出: java.io.PrintStream = Console.out) =
     |   输出.println("时间 = " + System.currentTimeMillis())
打印时间: (输出: java.io.PrintStream)Unit

scala> def 打印时间2(输出: java.io.PrintStream = Console.out,
     |               被除数: Int = 1) =
     |   输出.println("时间 = " + System.currentTimeMillis()/被除数)
打印时间2: (输出: java.io.PrintStream, 被除数: Int)Unit

scala> 打印时间2(输出 = Console.err)
时间 = 1543303507916

scala> 打印时间2(被除数 = 1000)
时间 = 1543303528

8.9 尾递归

7.2 中, 演示了通过递归将循环和var转变为函数式+val. 下面是递归例子(不完整, 需另外定义已够准改进):

def 估摸(猜测值: Double): Double =
  if (已够准(猜测值)) 猜测值
  else 估摸(改进(猜测值))

循环版本:

def 估摸循环(最初猜测: Double): Double = {
  var 猜测值 = 最初猜测
  while (!已够准(猜测值))
    猜测值 = 改进(猜测值)
  猜测值
}

运行时两个版本几乎一样快, 递归版本中估摸是最后调用的函数, 即"尾递归"

跟踪尾递归函数

def (: Int): Int =
  if (数 == 0) throw new Exception("爆!") else 炸(数 - 1) + 1

此函数非尾递归, 由于在递归函数调用后有+操作, 运行时会爆:

scala> 炸(3)
java.lang.Exception: 爆!
  at .炸(<console>:12)
  at .炸(<console>:12)
  at .炸(<console>:12)
  at .炸(<console>:12)
  ... 29 elided

如果改为尾递归:

def (: Int): Int =
  if (数 == 0) throw new Exception("爆!") else 爆(数 - 1)

调用时可见一层堆栈:

scala> 爆(5)
java.lang.Exception: 爆!
  at .爆(<console>:12)
  ... 29 elided

为确认这是尾递归优化的效果, 可在scala交互环境启动时加-g:notailcalls除去尾递归优化. 之后再运行可见多层:

scala> 爆(5)
java.lang.Exception: 爆!
  at .爆(<console>:12)
  at .爆(<console>:12)
  at .爆(<console>:12)
  at .爆(<console>:12)
  at .爆(<console>:12)
  at .爆(<console>:12)
  ... 29 elided

尾递归的局限

如果递归函数不是直接调用, 优化不能进行:

def 为偶数(: Int): Boolean =
  if (数 == 0) true else 为奇数(数 - 1)
def 为奇数(: Int): Boolean =
  if (数 == 0) false else 为偶数(数 - 1)

即使是下面也不能:

val 函数值 = 嵌套函数 _
def 嵌套函数(: Int): Unit = {
  if (数 != 0) { println(数); 函数值(数 - 1) }
}

(第八章完)