第6章——函数值和闭包

在函数式编程中,函数是一等公民。函数可以作为参数值传入其他函数中,函数的返回值可 以是函数,函数甚至可以嵌套函数。这些高阶函数在 Scala 中被称为函数值(function value)。闭 包(closure)是函数值的特殊形式,会捕获或者绑定到在另一个作用域或上下文中定义的变量。

6.1 常规函数的局限性

FunctionValuesAndClosures/Sum.scala

def sum(number: Int) = {
  var result = 0
  for (i ← 1 to number) {
    result += i
  }
  result
}
Full source at GitHub

6.2 可扩展性与高阶函数

FunctionValuesAndClosures/Loop.scala

def totalResultOverRange(number: Int, codeBlock: Int ⇒ Int) = {
  var result = 0
  for (i ← 1 to number) {
    result += codeBlock(i)
  }
  result
}
Full source at GitHub

FunctionValuesAndClosures/Loop.scala

println(totalResultOverRange(11, i ⇒ i))
Full source at GitHub

FunctionValuesAndClosures/Loop.scala

println(totalResultOverRange(11, i ⇒ if (i % 2 == 0) i else 0))
Full source at GitHub

FunctionValuesAndClosures/Loop.scala

println(totalResultOverRange(11, i ⇒ if (i % 2 != 0) i else 0))
Full source at GitHub

6.3 具有多个参数的函数值

FunctionValuesAndClosures/ZeroParam.scala

def printValue(generator: () ⇒ Int): Unit = {
  println(s"Generated value is ${generator()}")
}

printValue(() ⇒ 42)
Full source at GitHub

FunctionValuesAndClosures/Inject.scala

def inject(arr: Array[Int], initial: Int, operation: (Int, Int) ⇒ Int) = {
  var carryOver = initial
  arr.foreach(element ⇒ carryOver = operation(carryOver, element))
  carryOver
}
Full source at GitHub

FunctionValuesAndClosures/Inject.scala

val array = Array(2, 3, 5, 1, 6, 4)
val sum = inject(array, 0, (carry, elem) ⇒ carry + elem)
println(s"Sum of elements in array is $sum")
Full source at GitHub

FunctionValuesAndClosures/Inject.scala

val max =
  inject(array, Integer.MIN_VALUE, (carry, elem) ⇒ Math.max(carry, elem))
println(s"Max of elements in array is  $max")
Full source at GitHub

运行结果

Sum of elements in array is 21
Max of elements in array is  6
Full source at GitHub

FunctionValuesAndClosures/Inject2.scala

val sum = array.foldLeft(0)((sum, elem) ⇒ sum + elem) //可以被替换为对sum方法的调用
val max = array.foldLeft(Integer.MIN_VALUE) { (large, elem) ⇒
  Math.max(large, elem)
}

println(s"Sum of elements in array is $sum")
println(s"Max of elements in array is $max")
Full source at GitHub

FunctionValuesAndClosures/Inject2.scala

val sum = (0 /: array) { (sum, elem) ⇒ sum + elem }
val max =
  (Integer.MIN_VALUE /: array) { (large, elem) ⇒ Math.max(large, elem) }
Full source at GitHub

FunctionValuesAndClosures/Inject3.scala

val sum = inject(array, 0) { (carryOver, elem) ⇒ carryOver + elem }
Full source at GitHub

运行结果

Inject3.scala:9: error: not enough arguments for method inject: (arr:
Array[Int], initial: Int, operation: (Int, Int) => Int)Int.
Unspecified value parameter operation.
val sum = inject(array, 0) {(carryOver, elem) => carryOver + elem}
                ^
one error found
Full source at GitHub

6.4 柯里化


scala> def foo(a: Int)(b: Int)(c:Int) = {} foo: (a: Int)(b: Int)(c: Int)Unit scala> foo _ res0: Int => (Int => (Int => Unit)) = <function1> scala> :quit
Full source at GitHub

FunctionValuesAndClosures/Inject4.scala

def inject(arr: Array[Int], initial: Int)(operation: (Int, Int) ⇒ Int): Int = {
  var carryOver = initial
  arr.foreach(element ⇒ carryOver = operation(carryOver, element))
  carryOver
}
Full source at GitHub

FunctionValuesAndClosures/Inject4.scala

val sum: Int = inject(array, 0) { (carryOver, elem) ⇒ carryOver + elem }
Full source at GitHub

6.5 参数的占位符

FunctionValuesAndClosures/Underscore.scala

val arr = Array(1, 2, 3, 4, 5)

val total = (0 /: arr) { (sum, elem) ⇒ sum + elem }
Full source at GitHub

FunctionValuesAndClosures/Underscore.scala

val total = (0 /: arr) { _ + _ }
Full source at GitHub

FunctionValuesAndClosures/Underscore.scala

val negativeNumberExists1 = arr.exists { elem ⇒ elem < 0 }
val negativeNumberExists2 = arr.exists { _ < 0 }
Full source at GitHub

6.6 参数路由

FunctionValuesAndClosures/RouteParams.scala

val largest =
  (Integer.MIN_VALUE /: arr) { (carry, elem) ⇒ Math.max(carry, elem) }
Full source at GitHub

FunctionValuesAndClosures/RouteParams.scala

val largest = (Integer.MIN_VALUE /: arr) { Math.max(_, _) }
Full source at GitHub

FunctionValuesAndClosures/RouteParams.scala

val largest = (Integer.MIN_VALUE /: arr) { Math.max _ }
Full source at GitHub

FunctionValuesAndClosures/RouteParams.scala

val largest = (Integer.MIN_VALUE /: arr) { Math.max }
Full source at GitHub

6.7 复用函数值

FunctionValuesAndClosures/Equipment.scala

class Equipment(val routine: Int ⇒ Int) {
  def simulate(input: Int): Int = {
    print("Running simulation...")
    routine(input)
  }
}
Full source at GitHub

FunctionValuesAndClosures/EquipmentUseNotDry.scala

object EquipmentUseNotDry extends App {
  val equipment1 = new Equipment(
    { input ⇒ println(s"calc with $input"); input })
  val equipment2 = new Equipment(
    { input ⇒ println(s"calc with $input"); input })

  equipment1.simulate(4)
  equipment2.simulate(6)
}
Full source at GitHub

运行结果

Running simulation...calc with 4
Running simulation...calc with 6
Full source at GitHub

FunctionValuesAndClosures/EquipmentUseNotDry.scala

object EquipmentUseDry extends App {
  val calculator = { input: Int ⇒ println(s"calc with $input"); input }

  val equipment1 = new Equipment(calculator)
  val equipment2 = new Equipment(calculator)

  equipment1.simulate(4)
  equipment2.simulate(6)
}
Full source at GitHub

运行结果

Running simulation...calc with 4
Running simulation...calc with 6
Full source at GitHub

FunctionValuesAndClosures/EquipmentUseNotDry2.scala

object EquipmentUseDry2 extends App {
  def calculator(input: Int) = { println(s"calc with $input"); input }

  val equipment1 = new Equipment(calculator)
  val equipment2 = new Equipment(calculator)

  equipment1.simulate(4)
  equipment2.simulate(6)
}
Full source at GitHub

运行结果

Running simulation...calc with 4
Running simulation...calc with 6
Full source at GitHub

6.8 部分应用函数

FunctionValuesAndClosures/Log.scala

import java.util.Date

def log(date: Date, message: String): Unit = {
  //...
  println(s"$date ---- $message")
}

val date = new Date(1420095600000L)
log(date, "message1")
log(date, "message2")
log(date, "message3")
Full source at GitHub

FunctionValuesAndClosures/Log.scala

val date = new Date(1420095600000L)
val logWithDateBound = log(date, _: String)
logWithDateBound("message1")
logWithDateBound("message2")
logWithDateBound("message3")
Full source at GitHub

运行结果

scala> import java.util.Date
import java.util.Date

scala> def log(date: Date, message: String) =  println(s"$date ---- 
$message")
log: (date: java.util.Date, message: String)Unit

scala> val logWithDateBound = log(new Date, _ : String)
logWithDateBound: String => Unit = <function1>

scala> :quit
Full source at GitHub

6.9 闭包

FunctionValuesAndClosures/Closure.scala

def loopThrough(number: Int)(closure: Int ⇒ Unit): Unit = {
  for (i ← 1 to number) { closure(i) }
}
Full source at GitHub

FunctionValuesAndClosures/Closure.scala

var result = 0
val addIt = { value: Int ⇒ result += value }
Full source at GitHub

FunctionValuesAndClosures/Closure.scala

loopThrough(10) { elem ⇒ addIt(elem) }
println(s"Total of values from 1 to 10 is $result")

result = 0
loopThrough(5) { addIt }
println(s"Total of values from 1 to 5 is $result")
Full source at GitHub

FunctionValuesAndClosures/Closure.scala

var product = 1
loopThrough(5) { product *= _ }
println(s"Product of values from 1 to 5 is $product")
Full source at GitHub

运行结果

Total of values from 1 to 10 is 55
Total of values from 1 to 5 is 15
Product of values from 1 to 5 is 120
Full source at GitHub

6.10 Execute Around Method 模式

FunctionValuesAndClosures/Resource.scala

class Resource private () {
  println("Starting transaction...")
  private def cleanUp(): Unit = { println("Ending transaction...") }
  def op1(): Unit = println("Operation 1")
  def op2(): Unit = println("Operation 2")
  def op3(): Unit = println("Operation 3")
}

object Resource {
  def use(codeBlock: Resource ⇒ Unit): Unit = {
    val resource = new Resource
    try {
      codeBlock(resource)
    } finally {
      resource.cleanUp()
    }
  }
}
Full source at GitHub

FunctionValuesAndClosures/Resource.scala

Resource.use { resource ⇒
  resource.op1()
  resource.op2()
  resource.op3()
  resource.op1()
}
Full source at GitHub

运行结果

Starting transaction...
Operation 1
Operation 2
Operation 3
Operation 1
Ending transaction...
Full source at GitHub

FunctionValuesAndClosures/WriteToFile.scala

import java.io._

def writeToFile(fileName: String)(codeBlock: PrintWriter ⇒ Unit): Unit = {
  val writer = new PrintWriter(new File(fileName))
  try { codeBlock(writer) } finally { writer.close() }
}
Full source at GitHub

FunctionValuesAndClosures/WriteToFile.scala

writeToFile("output/output.txt") { writer ⇒
  writer write "hello from Scala"
}
Full source at GitHub

运行结果

hello from Scala
Full source at GitHub