泛函编程(11)-延后计算-lazy evaluation
延后计算(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的更多相关文章
- 泛函编程(13)-无穷数据流-Infinite Stream
上节我们提到Stream和List的主要分别是在于Stream的“延后计算“(lazy evaluation)特性.我们还讨论过在处理大规模排列数据集时,Stream可以一个一个把数据元素搬进内存并且 ...
- 泛函编程(12)-数据流-Stream
在前面的章节中我们介绍了List,也讨论了List的数据结构和操作函数.List这个东西从外表看上去挺美,但在现实中使用起来却可能很不实在.为什么?有两方面:其一,我们可以发现所有List的操作都是在 ...
- 泛函编程(19)-泛函库设计-Parallelism In Action
上节我们讨论了并行运算组件库的基础设计,实现了并行运算最基本的功能:创建新的线程并提交一个任务异步执行.并行运算类型的基本表达形式如下: import java.util.concurrent._ o ...
- 泛函编程(38)-泛函Stream IO:IO Process in action
在前面的几节讨论里我们终于得出了一个概括又通用的IO Process类型Process[F[_],O].这个类型同时可以代表数据源(Source)和数据终端(Sink).在这节讨论里我们将针对Proc ...
- 泛函编程(36)-泛函Stream IO:IO数据源-IO Source & Sink
上期我们讨论了IO处理过程:Process[I,O].我们说Process就像电视信号盒子一样有输入端和输出端两头.Process之间可以用一个Process的输出端与另一个Process的输入端连接 ...
- 泛函编程(35)-泛函Stream IO:IO处理过程-IO Process
IO处理可以说是计算机技术的核心.不是吗?使用计算机的目的就是希望它对输入数据进行运算后向我们输出计算结果.所谓Stream IO简单来说就是对一串按序相同类型的输入数据进行处理后输出计算结果.输入数 ...
- 泛函编程(32)-泛函IO:IO Monad
由于泛函编程非常重视函数组合(function composition),任何带有副作用(side effect)的函数都无法实现函数组合,所以必须把包含外界影响(effectful)副作用不纯代码( ...
- 泛函编程(7)-数据结构-List-折叠算法
折叠算法是List的典型算法.通过折叠算法可以实现众多函数组合(function composition).所以折叠算法也是泛函编程里的基本组件(function combinator).了解折叠算法 ...
- 泛函编程(6)-数据结构-List基础
List是一种最普通的泛函数据结构,比较直观,有良好的示范基础.List就像一个管子,里面可以装载一长条任何类型的东西.如需要对管子里的东西进行处理,则必须在管子内按直线顺序一个一个的来,这符合泛函编 ...
随机推荐
- [原创]自定义view之:快速开发一款Material Design风格的dialog的开源项目MDDialog
随着google开始主导Material Design风格的设计,越来越多的app开始使用Material Design风格来设计自己的UI.虽然在Android Studio中集成了多种快速开发框架 ...
- thinking in object pool
1.背景 对象池为了避免频繁创建耗时或耗资源的大对象,事先在对象池中创建好一定数量的大对象,然后尽量复用对象池中的对象,用户用完大对象之后放回对象池. 2.问题 目前纵观主流语言的实现方式无外乎3个步 ...
- Fix “Windows cannot access the specified device path or file” Error
http://helpdeskgeek.com/help-desk/windows-cannot-access-the-specified-device-path-or-file/ Method 1 ...
- Mac终端常用命令收集
删除非空目录 rm -rf 目录名字 -r 就是向下递归,不管有多少级目录,一并删除-f 就是直接强行删除,不作任何提示的意思 终端修改hosts文件 sudo vi /etc/hosts 切换到su ...
- 再谈LRU双链表内存管理
N年前我写了个双链表也发了博客,还添了代码.但是那个代码不但复杂,而且还有有问题的,一直懒得整理,放在空间误导别人.最近在写服务端,今天抽点空补一篇. 关于LRU网上随便搜,有过后端经验的人应该很多都 ...
- wireshark使用笔记
tcp && (ip.src==xxx.xxx.xxx.xxx || ip.dst==yyy.yyy.yyy.yyy)
- 电商大促准备流程v2
1 概述 对于电商企业而言,每年都会有几次大的促销活动,像双十一.店庆等,对于第一次参加这个活动的新手,难免会有些没有头绪,因而将自己参加双十一.双十二活动中的过程心得进行下总结,一方面供以后工作中继 ...
- 安卓开发笔记——重识Activity
Activity并不是什么新鲜的东西,老生常谈,这里只是随笔记录一些笔记. 每当说起Activity,感觉最关注的还是它的生命周期,因为要使我们的应用程序更加健壮,客户体验更加良好,如果对生命周期不熟 ...
- 解决php中echo出来的汉子乱码
问的人太多了,就列出来展示给大家! 需要了解的概念: Content-Type:用于定义用户的浏览器或相关设备如何显示将要加载的数据,或者如何处理将要加载的数据 MIME:MIME类型就是设定某种扩展 ...
- MyEclipse for Spring启动时报错"An internal error occurred during: 'Updating indexes'.Java heap space"的解决办法
问题 MyEclipse for Spring在启动时,报如下错误:An internal error occurred during: 'Updating indexes'.Java heap sp ...