第12章——惰性求值和并行集合

即时响应性是一项决定任何应用程序成败的关键因素。其他因素,如商业价值、易用性、 可用性、成本以及回弹性,也很重要,但是即时响应性是最重要的—我们人类大约需要 250 ms 来感知任何的移动,超过 5 s 的延迟就变得不可接受了。任何可以降低响应时间的努力都会 产生巨大的影响,能够使客户更加满意,进而赢得他们的信任。

12.1 释放惰性

Parallel/ShortCircuit.scala

def expensiveComputation() = {
  println("...assume slow operation...")
  false
}

def evaluate(input: Int): Unit = {
  println(s"evaluate called with $input")
  if (input >= 10 && expensiveComputation())
    println("doing work...")
  else
    println("skipping")
}

evaluate(0)
evaluate(100)
Full source at GitHub

运行结果

evaluate called with 0
skipping
evaluate called with 100
...assume slow operation...
skipping
Full source at GitHub

Parallel/Eager.scala

val perform = expensiveComputation()
if (input >= 10 && perform)
Full source at GitHub

运行结果

evaluate called with 0
...assume slow operation...
skipping
evaluate called with 100
...assume slow operation...
skipping
Full source at GitHub

Parallel/Eager.scala

@volatile lazy val perform = expensiveComputation()
if (input >= 10 && perform)
  println("doing work...")
Full source at GitHub

运行结果

evaluate called with 0
skipping
evaluate called with 100
...assume slow operation...
skipping
Full source at GitHub

Parallel/LazyOrder.scala

import scala.io._

def read = StdIn.readInt()

@volatile lazy val first = read
@volatile lazy val second = read

if (Math.random() < 0.5)
  second

println(first - second)
Full source at GitHub

运行结果

> scala LazyOrder.scala
1
2
1
> scala LazyOrder.scala
1
2
-1
>
Full source at GitHub

12.2 释放严格集合的惰性

Parallel/StrictCollection.scala

val people = List(("Mark", 32), ("Bob", 22), ("Jane", 8), ("Jill", 21),
  ("Nick", 50), ("Nancy", 42), ("Mike", 19), ("Sara", 12), ("Paula", 42),
  ("John", 21))

def isOlderThan17(person: (String, Int)) = {
  println(s"isOlderThan17 called for $person")
  val (_, age) = person
  age > 17
}

def isNameStartsWithJ(person: (String, Int)) = {
  println(s"isNameStartsWithJ called for $person")
  val (name, _) = person
  name.startsWith("J")
}

println(people.filter { isOlderThan17 }.filter { isNameStartsWithJ }.head)
Full source at GitHub

运行结果

isOlderThan17 called for (Mark,32)
isOlderThan17 called for (Bob,22)
isOlderThan17 called for (Jane,8)
isOlderThan17 called for (Jill,21)
isOlderThan17 called for (Nick,50)
isOlderThan17 called for (Nancy,42)
isOlderThan17 called for (Mike,19)
isOlderThan17 called for (Sara,12)
isOlderThan17 called for (Paula,42)
isOlderThan17 called for (John,21)
isNameStartsWithJ called for (Mark,32)
isNameStartsWithJ called for (Bob,22)
isNameStartsWithJ called for (Jill,21)
isNameStartsWithJ called for (Nick,50)
isNameStartsWithJ called for (Nancy,42)
isNameStartsWithJ called for (Mike,19)
isNameStartsWithJ called for (Paula,42)
isNameStartsWithJ called for (John,21)
(Jill,21)
Full source at GitHub

Parallel/LazyCollection.scala

println(people.view.filter { isOlderThan17 }.filter { isNameStartsWithJ }.head)
Full source at GitHub

运行结果

isOlderThan17 called for (Mark,32)
isNameStartsWithJ called for (Mark,32)
isOlderThan17 called for (Bob,22)
isNameStartsWithJ called for (Bob,22)
isOlderThan17 called for (Jane,8)
isOlderThan17 called for (Jill,21)
isNameStartsWithJ called for (Jill,21)
(Jill,21)
Full source at GitHub

12.3 终极惰性流

Parallel/NumberGenerator.scala

def generate(starting: Int): Stream[Int] = {
  starting #:: generate(starting + 1)
}

println(generate(25))
Full source at GitHub

运行结果

Stream(25, ?)
Full source at GitHub

Parallel/NumberGenerator.scala

println(generate(25).take(10).force)
println(generate(25).take(10).toList)
Full source at GitHub

运行结果

Stream(25, 26, 27, 28, 29, 30, 31, 32, 33, 34)
List(25, 26, 27, 28, 29, 30, 31, 32, 33, 34)
Full source at GitHub

Parallel/NumberGenerator.scala

println(generate(25).takeWhile { _ < 40 }.force)
Full source at GitHub

运行结果

Stream(25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39)
Full source at GitHub

Parallel/Primes.scala

def isDivisibleBy(number: Int, divisor: Int) = number % divisor == 0

def isPrime(number: Int) =
  number > 1 && !(2 until number).exists { isDivisibleBy(number, _) }

def primes(starting: Int): Stream[Int] = {
  println(s"computing for $starting")
  if (isPrime(starting))
    starting #:: primes(starting + 1)
  else
    primes(starting + 1)
}
Full source at GitHub

Parallel/Primes.scala

val primesFrom100 = primes(100)

println(primesFrom100.take(3).toList)
println("Let's ask for more...")
println(primesFrom100.take(4).toList)
Full source at GitHub

运行结果

computing for 100
computing for 101
computing for 102
computing for 103
computing for 104
computing for 105
computing for 106
computing for 107
List(101, 103, 107)
Let's ask for more...
computing for 108
computing for 109
List(101, 103, 107, 109)
Full source at GitHub

12.4 并行集合

Parallel/Weather.scala

import scala.io.Source
import scala.xml._

def getWeatherData(city: String) = {
  val response = Source.fromURL(
    s"https://raw.githubusercontent.com/ReactivePlatform/" +
      s"Pragmatic-Scala-StaticResources/master/src/main/resources/" +
      s"weathers/$city.xml")
  val xmlResponse = XML.loadString(response.mkString)
  val cityName = (xmlResponse \\ "city" \ "@name").text
  val temperature = (xmlResponse \\ "temperature" \ "@value").text
  val condition = (xmlResponse \\ "weather" \ "@value").text
  (cityName, temperature, condition)
}
Full source at GitHub

Parallel/Weather.scala

def printWeatherData(weatherData: (String, String, String)): Unit = {
  val (cityName, temperature, condition) = weatherData

  println(f"$cityName%-15s $temperature%-6s $condition")
}
Full source at GitHub

Parallel/Weather.scala

def timeSample(getData: List[String] ⇒ List[(String, String, String)]): Unit = {
  val cities = List("Houston,us", "Chicago,us", "Boston,us", "Minneapolis,us",
    "Oslo,norway", "Tromso,norway", "Sydney,australia", "Berlin,germany",
    "London,uk", "Krakow,poland", "Rome,italy", "Stockholm,sweden",
    "Bangalore,india", "Brussels,belgium", "Reykjavik,iceland")

  val start = System.nanoTime
  getData(cities).sortBy(_._1).foreach(printWeatherData)
  val end = System.nanoTime
  println(s"Time taken: ${(end - start) / 1.0e9} sec")
}
Full source at GitHub

Parallel/Weather.scala

timeSample { cities ⇒ cities map getWeatherData }
Full source at GitHub

运行结果

Bengaluru       84.2   few clouds
Berlin          45.63  broken clouds
Boston          52.23  scattered clouds
Brussels        50.83  Sky is Clear
Chicago         46.13  sky is clear
Cracow          40.39  moderate rain
Houston         54.01  light intensity drizzle
London          55.33  Sky is Clear
Minneapolis     42.82  sky is clear
Oslo            47.3   Sky is Clear
Reykjavik       31.17  proximity shower rain
Rome            58.42  few clouds
Stockholm       47.28  Sky is Clear
Sydney          68.9   Sky is Clear
Tromso          35.6   proximity shower rain
Time taken: 67.208944087 sec
Full source at GitHub

Parallel/Weather.scala

timeSample { cities ⇒ (cities.par map getWeatherData).toList }
Full source at GitHub

运行结果

Bengaluru       84.2   few clouds
Berlin          45.63  broken clouds
Boston          52.23  scattered clouds
Brussels        50.83  Sky is Clear
Chicago         46.13  sky is clear
Cracow          40.39  moderate rain
Houston         54.01  light intensity drizzle
London          55.33  Sky is Clear
Minneapolis     42.82  sky is clear
Oslo            47.3   Sky is Clear
Reykjavik       31.17  proximity shower rain
Rome            58.42  few clouds
Stockholm       47.28  Sky is Clear
Sydney          68.9   Sky is Clear
Tromso          35.6   proximity shower rain
Time taken: 0.171599394 sec
Full source at GitHub