第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
}

6.2 可扩展性与高阶函数

FunctionValuesAndClosures/Loop.scala

def totalResultOverRange(number: Int, codeBlock: Int => Int) = {
  var result = 0
  for (i <- 1 to number) {
    result += codeBlock(i)
  }
  result
}

FunctionValuesAndClosures/Loop.scala

println(totalResultOverRange(11, i => i))

FunctionValuesAndClosures/Loop.scala

println(totalResultOverRange(11, i => if (i % 2 == 0) i else 0))

FunctionValuesAndClosures/Loop.scala

println(totalResultOverRange(11, i => if (i % 2 != 0) i else 0))

6.3 具有多个参数的函数值

FunctionValuesAndClosures/ZeroParam.scala

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

printValue(() => 42)

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
}

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")

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")

运行结果

Sum of elements in array is 21
Max of elements in array is  6

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")

FunctionValuesAndClosures/Inject2.scala

val sum = (0 /: array) { (sum, elem) =>
  sum + elem
}
val max =
  (Integer.MIN_VALUE /: array) { (large, elem) =>
    Math.max(large, elem)
  }

FunctionValuesAndClosures/Inject3.scala

val sum = inject(array, 0) { (carryOver, elem) =>
  carryOver + elem
}

运行结果

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

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

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
}

FunctionValuesAndClosures/Inject4.scala

val sum: Int = inject(array, 0) { (carryOver, elem) =>
  carryOver + elem
}

6.5 参数的占位符

FunctionValuesAndClosures/Underscore.scala

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

val total = arr.foldLeft(0) { (sum, elem) =>
  sum + elem
}

FunctionValuesAndClosures/Underscore.scala

val total = arr.foldLeft(0) { _ + _ }

FunctionValuesAndClosures/Underscore.scala

val negativeNumberExists1 = arr.exists { elem =>
  elem < 0
}
val negativeNumberExists2 = arr.exists { _ < 0 }

6.6 参数路由

FunctionValuesAndClosures/RouteParams.scala

val largest =
  (Integer.MIN_VALUE /: arr) { (carry, elem) =>
    Math.max(carry, elem)
  }

FunctionValuesAndClosures/RouteParams.scala

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

FunctionValuesAndClosures/RouteParams.scala

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

FunctionValuesAndClosures/RouteParams.scala

val largest = (Integer.MIN_VALUE /: arr) { Math.max }

6.7 复用函数值

FunctionValuesAndClosures/Equipment.scala

class Equipment(val routine: Int => Int) {
  def simulate(input: Int): Int = {
    print("Running simulation...")
    routine(input)
  }
}

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)
}

运行结果

Running simulation...calc with 4
Running simulation...calc with 6

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)
}

运行结果

Running simulation...calc with 4
Running simulation...calc with 6

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)
}

运行结果

Running simulation...calc with 4
Running simulation...calc with 6

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")

FunctionValuesAndClosures/Log.scala

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

运行结果

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

6.9 闭包

FunctionValuesAndClosures/Closure.scala

def loopThrough(number: Int)(closure: Int => Unit): Unit = {
  for (i <- 1 to number) { closure(i) }
}

FunctionValuesAndClosures/Closure.scala

var result = 0
val addIt = { value: Int =>
  result += value
}

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")

FunctionValuesAndClosures/Closure.scala

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

运行结果

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

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()
    }
  }
}

FunctionValuesAndClosures/Resource.scala

Resource.use { resource =>
  resource.op1()
  resource.op2()
  resource.op3()
  resource.op1()
}

运行结果

Starting transaction...
Operation 1
Operation 2
Operation 3
Operation 1
Ending transaction...

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()
  }
}

FunctionValuesAndClosures/WriteToFile.scala

writeToFile("output/output.txt") { writer =>
  writer.write("hello from Scala")
}

运行结果

hello from Scala