加载中...

惰性求值/Call by name


维基百科中惰性求值的解释
惰性求值(Lazy Evaluation),又称惰性计算、懒惰求值,是一个计算机编程中的一个概念,它的目的是要最小化计算机要做的工作。它有两个相关而又有区别的含意,可以表示为“延迟求值”和“最小化求值”,本条目专注前者,后者请参见最小化计算条目。除可以得到性能的提升外,惰性计算的最重要的好处是它可以构造一个无限的数据类型。
惰性求值的相反是及早求值,这是一个大多数编程语言所拥有的普通计算方式。

惰性求值不是新鲜事

  1. import scala.io.Source.fromFile
  2. val iter: Iterator[String] =
  3. fromFile("sampleFile")
  4. .getLines()

文件迭代器就用到了惰性求值.
用户可以完全像操作内存中的数据一样操作文件,然而文件只有一小部分传入了内存中.

用lazy关键词指定惰性求值

  1. lazy val firstLazy = {
  2. println("first lazy")
  3. 1
  4. }
  5. lazy val secondLazy = {
  6. println("second lazy")
  7. 2
  8. } 
  9. def add(a:Int,b:Int) = {
  10. a+b
  11. }
  1. //在 scala repl 中的结果
  2. scala> add(secondLazy,firstLazy)
  3. second lazy
  4. first lazy
  5. res0: Int = 3
  6. res0: Int = 3

second lazy 先于 first lazy输出了

Call by value 就是函数参数的惰性求值

  1. def firstLazy = {
  2. println("first lazy")
  3. 1
  4. }
  5. def secondLazy = {
  6. println("second lazy")
  7. 2
  8. }
  9. def chooseOne(first: Boolean, a: Int, b: Int) = {
  10. if (first) a else b
  11. }
  12. def chooseOneLazy(first: Boolean, a: => Int, b: => Int) = {
  13. if (first) a else b
  14. }
  1. chooseOne(first = true, secondLazy, firstLazy)
  2. //second lazy
  3. //first lazy
  4. //res0: Int = 2
  5. chooseOneLazy(first = true, secondLazy, firstLazy)
  6. //second lazy
  7. //res1: Int = 2

对于非纯函数,惰性求值会产生和立即求值产生不一样的结果.

一个例子,假设你要建立一个本地缓存

  1. //需要查询mysql等,可能来自于一个第三方jar包
  2. def itemIdToShopId: Int => Int  
  3. var cache = Map.empty[Int, Int]
  4. def cachedItemIdToShopId(itemId: Int):Int = {
  5. cache.get(itemId) match {
  6. case Some(shopId) => shopId
  7. case None =>
  8. val shopId = itemIdToShopId(itemId)
  9. cache += itemId -> shopId
  10. shopId
  11. }
  12. }
  • 罗辑没什么问题,但测试的时候不方便连mysql怎么办?
  • 如果第三方jar包发生了改变,cachedItemIdToShopId也要发生改变.
  1. //用你的本地mock来测试程序
  2. def mockItemIdToSHopId: Int => Int
  3. def cachedItemIdToShopId(itemId: Int): Int ={
  4. cache.get(itemId) match {
  5. case Some(shopId) => shopId
  6. case None =>
  7. val shopId = mockItemIdToSHopId(itemId)
  8. cache += itemId -> shopId
  9. shopId
  10. }
  11. }
  • 在测试的时候用mock,提交前要换成线上的,反复测试的话要反复改动,非常令人沮丧.
  • 手工操作容易忙中出错.
  1. //将远程请求的结果作为函数的一个参数
  2. def cachedItemIdToShopId(itemId: Int, remoteShopId: Int): Int = {
  3. cache.get(itemId) match {
  4. case Some(shopId) => shopId
  5. case None =>
  6. val shopId = remoteShopId
  7. cache += itemId -> shopId
  8. shopId
  9. }
  10. }
  11. //调用这个函数
  12. cachedItemIdToShopId(itemId,itemIdToShopId(itemId))
  • 函数对mysql的依赖没有了
  • 不需要在测试和提交时切换代码
  • 貌似引入了新问题?

没错,cache根本没有起应有的作用,函数每次执行的时候都调用了itemIdToShopId从远程取数据

  1. //改成call by name就没有这个问题啦
  2. def cachedItemIdToShopId(itemId: Int, remoteShopId: =>Int): Int = {
  3. cache.get(itemId) match {
  4. case Some(shopId) => shopId
  5. case None =>
  6. val shopId = remoteShopId
  7. cache += itemId -> shopId
  8. shopId
  9. }
  10. }
  11. //调用这个函数
  12. cachedItemIdToShopId(itemId,itemIdToShopId(itemId))
  • 函数对mysql的依赖没有了
  • 不需要在测试和提交时切换代码
  • 只在需要的时候查询远程库

还没有评论.