延后计算(lazy evaluation)是指将一个表达式的值计算向后拖延直到这个表达式真正被使用的时候。在讨论lazy-evaluation之前,先对泛函编程中比较特别的一个语言属性”计算时机“(strict-ness)做些介绍。strict-ness是指系统对一个表达式计算值的时间点模式:即时计算的(strict),或者延后计算的(non-strict or lazy)。non-strict或者lazy的意思是在使用一个表达式时才对它进行计值。用个简单直观的例子说明吧:

   def lazyFun(x: Int): Int = {
println("inside function")
x + 1
} //> lazyFun: (x: Int)Int
lazyFun(3/0) //> java.lang.ArithmeticException: / by zero

很明显,当我们把 3/0 作为参数传入lazyFun时,系统在进入函数前先计算这个参数的值,计算出现了异常,结果没进入函数执行println就直接退出了。下面我们把lazyFun的参数声明改一下变为:x: => Int:

  def lazyFun(x: => Int): Int = {
println("inside function")
x + 1
} //> lazyFun: (x: => Int)Int
lazyFun(3/0) //> inside function
//| java.lang.ArithmeticException: / by zero
//| at ch5.stream$$anonfun$main$1$$anonfun$1.apply$mcI$sp(ch5.stream.scala:1
//| 0)

在这个例子里我们再次向lazyFun传入了一个Exception。系统这次进入了函数内部,我们看到println("inside function")还是运行了。这表示系统并没有理会传入的参数,直到表达式x + 1使用这个参数x时才计算x的值。我们看到参数x的类型是 => Int, 代表x参数是non-strict的。non-strict参数每次使用时都会重新计算一次。从内部实现机制来解释:这是因为编译器(compiler)遇到non-strict参数时会把一个指针放到调用堆栈里,而不是惯常的把参数的值放入。所以每次使用non-strict参数时都会重新计算一下。我们可以从下面的例子得到证实:

   def pair(x: => Int):(Int, Int) = (x, x)         //> pair: (x: => Int)(Int, Int)
pair( {println("hello..."); 5} ) //> hello...
//| hello...
//| res1: (Int, Int) = (5,5)

以上例子里我们向pair函数传入了一段以Int类 5 为结果的代码作为x参数。在返回了结果(5,5)后从两条hello...可以确认传入的参数被计算了两次。

实际上很多语言中的布尔表达式(Boolean Expression)都是non-strict的,包括 &&, || 。  x && y 表达式中如果x值为false的话系统不会去计算y的值,而是直接得出结果false。同样 x || y 中如x值为true时系统不会计算y。试想想如果y需要几千行代码来计算的话能节省多少计算资源。

再看看以下一个if-then-else例子:

  def if2[A](cond: Boolean, valTrue: => A, valFalse: => A): A = {
if (cond) { println("run valTrue..."); valTrue }
else { println("run valFalse..."); valFalse }
} //> if2: [A](cond: Boolean, valTrue: => A, valFalse: => A)A
if2(true, 1, 0) //> run valTrue...
//| res2: Int = 1
if2(false, 1, 0) //> run valFalse...
//| res3: Int = 0

if-then-else函数if2的参数中if条件是strict的,而then和else都是non-strict的。

可以看出到底运算valTrue还是valFalse皆依赖条件cond的运算结果。但无论如何系统只会按运算一个。还是那句,如果valTrue和valFalse都是几千行代码的大型复杂计算,那么non-strict特性会节省大量的计算资源,提高系统运行效率。除此之外,non-strict特性是实现无限数据流(Infinite Stream)的基本要求,这部分在下节Stream里会详细介绍。

不过从另一个方面分析:non-strict参数在函数内部有可能多次运算;如果这个函数内部多次使用了这个参数。同样道理,如果这个参数是个大型计算的话,又会产生浪费资源的结果。在Scala语言中lazy声明可以解决non-strict参数多次运算问题。lazy值声明(lazy val)不但能延后赋值表达式的右边运算,还具有缓存(cache)的作用:在真正使时才运算表达式右侧,一旦赋值后不再重新计算。我们试着把上面的例子做些修改:

   def pair(x: => Int):(Int, Int) = {                    //> pair: (x: => Int)(Int, Int)
lazy val y = x //不运算,还没开始使用y
(y,y) //第一个y运算,第二个就使用缓存值了
}

这这个版本里我们使用了一个延缓值(lazy val)y。当调用这个函数时,参数的值运算在第一次使用y时会运算一次,然后存入缓存(cache),之后使用y时就无需重复计算,直接使用缓存值(cached value)。可以看看函数的调用结果:

   pair( { println("hello..."); 5} )               //> hello...
//| res1: (Int, Int) = (5,5)

同样产生了重复值(5,5),但参数值运算只进行了一次,因为只有一行hello...

泛函编程(11)-延后计算-lazy evaluation的更多相关文章

  1. 泛函编程(13)-无穷数据流-Infinite Stream

    上节我们提到Stream和List的主要分别是在于Stream的“延后计算“(lazy evaluation)特性.我们还讨论过在处理大规模排列数据集时,Stream可以一个一个把数据元素搬进内存并且 ...

  2. 泛函编程(12)-数据流-Stream

    在前面的章节中我们介绍了List,也讨论了List的数据结构和操作函数.List这个东西从外表看上去挺美,但在现实中使用起来却可能很不实在.为什么?有两方面:其一,我们可以发现所有List的操作都是在 ...

  3. 泛函编程(19)-泛函库设计-Parallelism In Action

    上节我们讨论了并行运算组件库的基础设计,实现了并行运算最基本的功能:创建新的线程并提交一个任务异步执行.并行运算类型的基本表达形式如下: import java.util.concurrent._ o ...

  4. 泛函编程(38)-泛函Stream IO:IO Process in action

    在前面的几节讨论里我们终于得出了一个概括又通用的IO Process类型Process[F[_],O].这个类型同时可以代表数据源(Source)和数据终端(Sink).在这节讨论里我们将针对Proc ...

  5. 泛函编程(36)-泛函Stream IO:IO数据源-IO Source & Sink

    上期我们讨论了IO处理过程:Process[I,O].我们说Process就像电视信号盒子一样有输入端和输出端两头.Process之间可以用一个Process的输出端与另一个Process的输入端连接 ...

  6. 泛函编程(35)-泛函Stream IO:IO处理过程-IO Process

    IO处理可以说是计算机技术的核心.不是吗?使用计算机的目的就是希望它对输入数据进行运算后向我们输出计算结果.所谓Stream IO简单来说就是对一串按序相同类型的输入数据进行处理后输出计算结果.输入数 ...

  7. 泛函编程(32)-泛函IO:IO Monad

    由于泛函编程非常重视函数组合(function composition),任何带有副作用(side effect)的函数都无法实现函数组合,所以必须把包含外界影响(effectful)副作用不纯代码( ...

  8. 泛函编程(7)-数据结构-List-折叠算法

    折叠算法是List的典型算法.通过折叠算法可以实现众多函数组合(function composition).所以折叠算法也是泛函编程里的基本组件(function combinator).了解折叠算法 ...

  9. 泛函编程(6)-数据结构-List基础

    List是一种最普通的泛函数据结构,比较直观,有良好的示范基础.List就像一个管子,里面可以装载一长条任何类型的东西.如需要对管子里的东西进行处理,则必须在管子内按直线顺序一个一个的来,这符合泛函编 ...

随机推荐

  1. 温故而知新:Delegate,Action,Func,匿名方法,匿名委托,事件

    Tks: http://www.cnblogs.com/yjmyzz/archive/2009/11/23/1608818.html 20150801 add: http://www.cnblogs. ...

  2. jquerymobile标签-属性手册

    Button data-role="button"   data-corners true | false data-icon home | delete | plus | arr ...

  3. C# Json帮助类

    using System; using System.Collections.Generic; using System.Web; using System.Text; using System.Re ...

  4. 【redmine】密码忘了后重新设置

    有段时间没有使用,忘记了原来的密码,搜索网上有一篇文章,不过版本比较老,和现在文件位置不一样,参考后成功重置了密码,感谢原作者. 原文内第一步一般是不需要的.主要是用ruby命令修改数据库内容. 进入 ...

  5. Phantomjs 一些简单实用

    Phantomjs是一个基于webkit的服务器端JavaScirpt API.它全面支持web而不需要浏览器支持,并且原生支持web的各种标准:DOM处理,CSS选择器,JSON,Canvas和SV ...

  6. Why is processing a sorted array faster than an unsorted array?

    这是我在逛 Stack Overflow 时遇见的一个高分问题:Why is processing a sorted array faster than an unsorted array?,我觉得这 ...

  7. ArcGIS“一个或多个ActiveX控件无法显示...”问题的解决方案

    ArcMap启动时的一个警告信息“一个或多个ActiveX控件无法显示...”,如图 出现这种情况,有可能的原因是IE浏览器的安全选项设置被修改了.比如被手动修改过,或者被第三方系统杀毒优化软件修改了 ...

  8. CSS实例

    CSS 实例 CSS背景 设置页面的背景颜色 设置不同元素的背景颜色 设置一个图像作为页面的背景 错误的的背景图片 如何在水平方向重复背景图像 如何定位背景图像 一个固定的背景图片(这个图片不会随页面 ...

  9. AssetBundle系列——资源的加载、简易的资源管理器

    每个需要进行资源管理的类都继承自IAssetManager,该类维护它所使用到的所有资源的一个资源列表.并且每个资源管理类可以重写其资源引用接口和解引用接口. 每个管理器有自己的管理策略,比如Scen ...

  10. AppStore下载慢的真实原因

    今天有个朋友说他的app下载很慢,他说下载其他的不会,就他的会很慢很卡.我心想这东西苹果的我也没办法了呀,不过我心里也想我平时下载挺快的呀.于是就开始试试.搜索了几个app下载发现确实是有快有慢,并且 ...