> 維基百科中惰性求值的解釋
> 惰性求值(Lazy Evaluation),又稱惰性計算、懶惰求值,是一個計算機編程中的一個概念,它的目的是要最小化計算機要做的工作。它有兩個相關而又有區別的含意,可以表示為“延遲求值”和“最小化求值”,本條目專注前者,后者請參見最小化計算條目。除可以得到性能的提升外,惰性計算的最重要的好處是它可以構造一個無限的數據類型。
> 惰性求值的相反是及早求值,這是一個大多數編程語言所擁有的普通計算方式。
### 惰性求值不是新鮮事
~~~
import scala.io.Source.fromFile
val iter: Iterator[String] =
fromFile("sampleFile")
.getLines()
~~~
文件迭代器就用到了惰性求值.
用戶可以完全像操作內存中的數據一樣操作文件,然而文件只有一小部分傳入了內存中.
### 用lazy關鍵詞指定惰性求值
~~~
lazy val firstLazy = {
println("first lazy")
1
}
lazy val secondLazy = {
println("second lazy")
2
}?
def add(a:Int,b:Int) = {
a+b
}
~~~
~~~
//在 scala repl 中的結果
scala> add(secondLazy,firstLazy)
second lazy
first lazy
res0: Int = 3
res0: Int = 3
~~~
second lazy 先于 first lazy輸出了
### Call by value 就是函數參數的惰性求值
~~~
def firstLazy = {
println("first lazy")
1
}
def secondLazy = {
println("second lazy")
2
}
def chooseOne(first: Boolean, a: Int, b: Int) = {
if (first) a else b
}
def chooseOneLazy(first: Boolean, a: => Int, b: => Int) = {
if (first) a else b
}
~~~
~~~
chooseOne(first = true, secondLazy, firstLazy)
//second lazy
//first lazy
//res0: Int = 2
chooseOneLazy(first = true, secondLazy, firstLazy)
//second lazy
//res1: Int = 2
~~~
對于非純函數,惰性求值會產生和立即求值產生不一樣的結果.
### 一個例子,假設你要建立一個本地緩存
~~~
//需要查詢mysql等,可能來自于一個第三方jar包
def itemIdToShopId: Int => Int ?
var cache = Map.empty[Int, Int]
def cachedItemIdToShopId(itemId: Int):Int = {
cache.get(itemId) match {
case Some(shopId) => shopId
case None =>
val shopId = itemIdToShopId(itemId)
cache += itemId -> shopId
shopId
}
}
~~~
* 羅輯沒什么問題,但測試的時候不方便連mysql怎么辦?
* 如果第三方jar包發生了改變,cachedItemIdToShopId也要發生改變.
~~~
//用你的本地mock來測試程序
def mockItemIdToSHopId: Int => Int
def cachedItemIdToShopId(itemId: Int): Int ={
cache.get(itemId) match {
case Some(shopId) => shopId
case None =>
val shopId = mockItemIdToSHopId(itemId)
cache += itemId -> shopId
shopId
}
}
~~~
* 在測試的時候用mock,提交前要換成線上的,反復測試的話要反復改動,非常令人沮喪.
* 手工操作容易忙中出錯.
~~~
//將遠程請求的結果作為函數的一個參數
def cachedItemIdToShopId(itemId: Int, remoteShopId: Int): Int = {
cache.get(itemId) match {
case Some(shopId) => shopId
case None =>
val shopId = remoteShopId
cache += itemId -> shopId
shopId
}
}
//調用這個函數
cachedItemIdToShopId(itemId,itemIdToShopId(itemId))
~~~
* 函數對mysql的依賴沒有了
* 不需要在測試和提交時切換代碼
* 貌似引入了新問題?
沒錯,cache根本沒有起應有的作用,函數每次執行的時候都調用了itemIdToShopId從遠程取數據
~~~
//改成call by name就沒有這個問題啦
def cachedItemIdToShopId(itemId: Int, remoteShopId: =>Int): Int = {
cache.get(itemId) match {
case Some(shopId) => shopId
case None =>
val shopId = remoteShopId
cache += itemId -> shopId
shopId
}
}
//調用這個函數
cachedItemIdToShopId(itemId,itemIdToShopId(itemId))
~~~
* 函數對mysql的依賴沒有了
* 不需要在測試和提交時切換代碼
* 只在需要的時候查詢遠程庫