惰性求值/Call by name

最后更新于:2022-04-01 23:05:32

> 维基百科中惰性求值的解释 > 惰性求值(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的依赖没有了 * 不需要在测试和提交时切换代码 * 只在需要的时候查询远程库
';