CGFT认证:惰性求值lazy evaluation与按名调用Call by name研究应用



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

“惰性求值”绝不是新鲜事

import scala.io.Source.fromFileval 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 lazyfirst lazyres0: Int = 3res0: 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 = 2chooseOneLazy(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 => Intdef 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的依赖没有了。
  • 不需要在测试和提交时切换代码。
  • 只在需要的时候查询远程库。
发表评论
留言与评论(共有 0 条评论) “”
   
验证码:

相关文章

推荐文章