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

运行结果

evaluate called with 0
skipping
evaluate called with 100
...assume slow operation...
skipping

Parallel/Eager.scala

val perform = expensiveComputation()
if (input >= 10 && perform)

运行结果

evaluate called with 0
...assume slow operation...
skipping
evaluate called with 100
...assume slow operation...
skipping

Parallel/Eager.scala

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

运行结果

evaluate called with 0
skipping
evaluate called with 100
...assume slow operation...
skipping

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)

运行结果

> scala LazyOrder.scala
1
2
1
> scala LazyOrder.scala
1
2
-1
>

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)

运行结果

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)

Parallel/LazyCollection.scala

println(people.view.filter { isOlderThan17 }.filter { isNameStartsWithJ }.head)

运行结果

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)

12.3 终极惰性流

Parallel/NumberGenerator.scala

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

println(generate(25))

运行结果

Stream(25, ?)

Parallel/NumberGenerator.scala

println(generate(25).take(10).force)
println(generate(25).take(10).toList)

运行结果

Stream(25, 26, 27, 28, 29, 30, 31, 32, 33, 34)
List(25, 26, 27, 28, 29, 30, 31, 32, 33, 34)

Parallel/NumberGenerator.scala

println(generate(25).takeWhile { _ < 40 }.force)

运行结果

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

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

Parallel/Primes.scala

val primesFrom100 = primes(100)

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

运行结果

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)

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

Parallel/Weather.scala

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

  println(f"$cityName%-15s $temperature%-6s $condition")
}

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

Parallel/Weather.scala

timeSample { cities =>
  cities.map(getWeatherData)
}

运行结果

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

Parallel/Weather.scala

import scala.collection.parallel.CollectionConverters._
timeSample { cities =>
  cities.par.map(getWeatherData).toList
}

运行结果

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