Scalaz(35)- Free :运算-Trampoline,say NO to StackOverflowError
在前面几次讨论中我们介绍了Free是个产生Monad的最基本结构。它的原理是把一段程序(AST)一连串的运算指令(ADT)转化成数据结构存放在内存里,这个过程是个独立的功能描述过程。然后另一个独立运算过程的Interpreter会遍历(traverse)AST结构,读取结构里的运算指令,实际运行指令。这里的重点是把一连串运算结构化(reify)延迟运行,具体实现方式是把Monad的连续运算方法flatMap转化成一串Suspend结构(case class),把运算过程转化成创建(construct)Suspend过程。flatMap的表现形式是这样的:flatMap(a => flatMap(b => flatMap(c => ....))),这是是明显的递归算法,很容易产生堆栈溢出异常(StackOverflow Exception),无法保证程序的安全运行,如果不能有效解决则FP编程不可行。Free正是解决这个问题的有效方法,因为它把Monad的递归算法flatMap转化成了一个创建数据结构实例的过程。每创建一个Suspend,立即完成一个运算。我们先用个例子来证明Monad flatMap的递归算法问题:
ef zipIndex[A](xa: List[A]): List[(Int,A)] =
xa.foldLeft(State.state[Int,List[(Int,A)]](List()))(
(acc,a) => for {
xn <- acc
s <- get[Int]
_ <- put[Int](s+)
} yield ((s,a) :: xn)
).eval().reverse //> zipIndex: [A](xa: List[A])List[(Int, A)] zipIndex( |-> ) //> res6: List[(Int, Int)] = List((1,1), (2,2), (3,3), (4,4), (5,5), (6,6), (7,7), (8,8), (9,9), (10,10))
zipIndex( |-> ) //> java.lang.StackOverflowError
在这个例子里我们使用了State Monad。我们知道for-comprehension就是flatMap链条,是一种递归算法,所以当zipIndex针对大List时就产生了StackOverflowError。
我们提到过用Trampoline可以heap换stack,以遍历数据结构代替递归运算来实现运行安全。那么什么是Trampoline呢?
sealed trait Trampoline[+A]
case class Done[A](a: A) extends Trampoline[A]
case class More[A](k: () => Trampoline[A]) extends Trampoline[A]
Trampoline就是一种数据结构。它有两种状态:Done(a: A)代表结构内存放了一个A值,More(k: ()=>Trampoline[A])代表结构内存放的是一个Function0函数,就是一个运算Trampoline[A]。
我们先试个递归算法例子:
def isEven(xa: List[Int]): Boolean =
xa match {
case Nil => true
case h :: t => isOdd(t)
} //> isEven: (xa: List[Int])Boolean
def isOdd(xa: List[Int]): Boolean =
xa match {
case Nil => false
case h :: t => isEven(t)
} //> isOdd: (xa: List[Int])Boolean isOdd( |-> ) //> res0: Boolean = true
isEven( |-> ) //> java.lang.StackOverflowError
可以看到isEven和isOdd这两个函数相互递归调用,最终用大点的List就产生了StackOverflowError。
现在重新调整一下函数isEven和isOdd的返回结构类型:从Boolean换成Trampoline,意思是从返回一个结果值变成返回一个数据结构:
def even(xa: List[Int]): Trampoline[Boolean] =
xa match {
case Nil => Done(true)
case h :: t => More(() => odd(t))
} //> even: (xa: List[Int])Exercises.trampoline.Trampoline[Boolean]
def odd(xa: List[Int]): Trampoline[Boolean] =
xa match {
case Nil => Done(false)
case h :: t => More(() => even(t))
} //> odd: (xa: List[Int])Exercises.trampoline.Trampoline[Boolean] even( |-> ) //> res0: Exercises.trampoline.Trampoline[Boolean] = More(<function0>)
现在我们获得了一个在heap上存放了123001个元素的数据结构More(<function0>)。这是一个在内存heap上存放的过程,并没有任何实质运算。
现在我们需要一个方法来遍历这个返回的结构,逐个运行结构中的function0:
sealed trait Trampoline[+A] {
final def runT: A =
this match {
case Done(a) => a
case More(k) => k().runT
}
}
even( |-> ).runT //> res0: Boolean = false
由于这个runT是个尾递归(Tail Call Elimination TCE)算法,所以没有出现StackOverflowError。
实际上scalaz也提供了Trampoline类型:scalaz/Free.scala
/** A computation that can be stepped through, suspended, and paused */
type Trampoline[A] = Free[Function0, A]
...
object Trampoline extends TrampolineInstances { def done[A](a: A): Trampoline[A] =
Free.Return[Function0,A](a) def delay[A](a: => A): Trampoline[A] =
suspend(done(a)) def suspend[A](a: => Trampoline[A]): Trampoline[A] =
Free.Suspend[Function0, A](() => a)
}
Trampoline就是Free[S,A]的一种特例。S == Function0,或者说Trampoline就是Free针对Function0生成的Monad,因为我们可以用Free.Return和Free.Suspend来实现Done和More。我们可以把scalaz的Trampoline用在even,odd函数里:
import scalaz.Free.Trampoline
def even(xa: List[Int]): Trampoline[Boolean] =
xa match {
case Nil => Trampoline.done(true)
case h :: t => Trampoline.suspend(odd(t))
} //> even: (xa: List[Int])scalaz.Free.Trampoline[Boolean]
def odd(xa: List[Int]): Trampoline[Boolean] =
xa match {
case Nil => Trampoline.done(false)
case h :: t => Trampoline.suspend(even(t))
} //> odd: (xa: List[Int])scalaz.Free.Trampoline[Boolean] even( |-> ).run //> res0: Boolean = false
语法同我们自定义的Trampoline差不多。
那么我们能不能把Trampoline用在上面的哪个zipIndex函数里来解决StackOverflowError问题呢?zipIndex里造成问题的Monad是个State Monad,我们可以用State.lift把State[S,A升格成StateT[Trampoline,S,A]。先看看这个lift函数:scalaz/StateT.scala
def lift[M[_]: Applicative]: IndexedStateT[({type λ[α]=M[F[α]]})#λ, S1, S2, A] = new IndexedStateT[({type λ[α]=M[F[α]]})#λ, S1, S2, A] {
def apply(initial: S1): M[F[(S2, A)]] = Applicative[M].point(self(initial))
}
这个函数的功能等于是:State.lift[Trampoline] >>> StateT[Tarmpoline,S,A]。先看另一个简单例子:
def incr: State[Int, Int] = State {s => (s+, s)}//> incr: => scalaz.State[Int,Int]
incr.replicateM().eval() take //> java.lang.StackOverflowError
import scalaz.Free.Trampoline
incr.lift[Trampoline].replicateM().eval().run.take()
//> res0: List[Int] = List(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
上面这个例子也使用了State Monad:函数incr返回的是State,这时用replicateM(10000).eval(0)对重复对10000个State进行运算时产生了StackOverflowError。我们跟着用lift把incr返回类型变成StateT[Trampoline,S,A],这时replicateM(10000).eval(0)的作用就是进行结构转化了(State.apply:Trampoline[(S,A)]),再用Trampoline.run作为Interpreter遍历结构进行运算。用lift升格Trampoline后解决了StackOverflowError。
我们试着调整一下zipIndex函数:
def safeZipIndex[A](xa: List[A]): List[(Int,A)] =
(xa.foldLeft(State.state[Int,List[(Int,A)]](List()))(
(acc,a) => for {
xn <- acc
s <- get[Int]
_ <- put(s + )
} yield (s,a) :: xn
).lift[Trampoline]).eval().run.reverse //> safeZipIndex: [A](xa: List[A])List[(Int, A)] safeZipIndex( |-> ).take() //> res2: List[(Int, Int)] = List((1,1), (2,2), (3,3), (4,4), (5,5), (6,6), (7,7), (8,8), (9,9), (10,10))
表面上来看结果好像是正确的。试试大一点的List:
safeZipIndex( |-> ).take() //> java.lang.StackOverflowError
//| at scalaz.IndexedStateT$$anonfun$flatMap$1.apply(StateT.scala:62)
//| at scalaz.IndexedStateT$$anon$10.apply(StateT.scala:95)
//| at scalaz.IndexedStateT$$anonfun$flatMap$1.apply(StateT.scala:62)
...
还是StackOverflowError,看错误提示是State.flatMap造成的。看来迟点还是按照incr的原理把foldLeft运算阶段结果拆分开来分析才行。
以上我们证明了Trampoline可以把连续运算转化成创建数据结构,以heap内存换stack,能保证递归算法运行的安全。因为Trampoline是Free的一个特例,所以Free的Interpreter也就可以保证递归算法安全运行。现在可以得出这样的结论:FP就是Monadic Programming,就是用Monad来编程,我们应该尽量用Free来生成Monad,用Free进行编程以保证FP程序的可靠性。
Scalaz(35)- Free :运算-Trampoline,say NO to StackOverflowError的更多相关文章
- Scalaz(53)- scalaz-stream: 程序运算器-application scenario
从上面多篇的讨论中我们了解到scalaz-stream代表一串连续无穷的数据或者程序.对这个数据流的处理过程就是一个状态机器(state machine)的状态转变过程.这种模式与我们通常遇到的程序流 ...
- Scalaz(47)- scalaz-stream: 深入了解-Source
scalaz-stream库的主要设计目标是实现函数式的I/O编程(functional I/O).这样用户就能使用功能单一的基础I/O函数组合成为功能完整的I/O程序.还有一个目标就是保证资源的安全 ...
- Scalaz(44)- concurrency :scalaz Future,尚不完整的多线程类型
scala已经配备了自身的Future类.我们先举个例子来了解scala Future的具体操作: import scala.concurrent._ import ExecutionContext. ...
- Smarty数学运算
数学运算可以直接应用到变量 Example 3-5. math examples 例 3-5.数学运算的例子 {$foo+1} {$foo*$bar} {* some more complicat ...
- C/C+小记
1.struct与typedef struct struct Student{int a;int b}stu1; //定义名为Student的结构体,及一个Student变量stu1 struct { ...
- java实现随机四则运算
使用JAVA编程语言,独立完成一个包含3到5个数字的四则运算练习,软件基本功能要求如下: 程序可接收一个输入参数n,然后随机产生n道加减乘除练习题,每个数字在 0 和 100 之间,运算符在3个到5个 ...
- kotlin递归&尾递归优化
递归: 对于递归最经典的应用当然就是阶乘的计算啦,所以下面用kotlin来用递归实现阶乘的计算: 编译运行: 那如果想看100的阶乘是多少呢? 应该是结果数超出了Int的表述范围,那改成Long型再试 ...
- Scalaz(50)- scalaz-stream: 安全的无穷运算-running infinite stream freely
scalaz-stream支持无穷数据流(infinite stream),这本身是它强大的功能之一,试想有多少系统需要通过无穷运算才能得以实现.这是因为外界的输入是不可预料的,对于系统本身就是无穷的 ...
- Scalaz(56)- scalaz-stream: fs2-安全运算,fs2 resource safety
fs2在处理异常及资源使用安全方面也有比较大的改善.fs2 Stream可以有几种方式自行引发异常:直接以函数式方式用fail来引发异常.在纯代码里隐式引发异常或者在运算中引发异常,举例如下: /函数 ...
随机推荐
- Atitit vod ver 12 new feature v12 pb2 影吧 视频 电影 点播 播放系统v12新特性
Atitit vod ver 12 new feature v12 pb2 影吧 视频 电影 点播 播放系统v12新特性 项目分离从独立的se ver Run mode from brow ex to ...
- 学习ASP.NET MVC(二)——我的第一个ASP.NET MVC 控制器
MVC全称是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,是一种软件设计典范,用一种业务逻辑和数据显示分离的方法组织代码,将 ...
- Chrome同步最新host文件IP列表
使用Chrome的童靴是不是很多都碰到同步问题呢?网上查来查去的都是给些host文件的修改,可是都是几年前的东西,地址都不对了,想想还是自己找到需要解析的域名的IP地址吧 步骤: 1.DNS设置为8. ...
- 学习bootstrap遇到的问题--001 关于bootstrap中类.disabled不禁用默认行为
自学bootstrap遇到的疑惑篇: 按钮状态--禁用 在Bootstrap框架中,要禁用按钮有两种实现方式: 方法1:在标签中添加disabled属性 方法2:在元素标签中添加类名"dis ...
- android NDK 生成so 文件流程-ecplice
1:生成jni目录 首先说一句网上,大部分博客这么写的:打开控制台,进入项目目录,运行javah -classpath bin/classes -d jni com.example.hellojni. ...
- Android TextView高级特性使用
TextView一般都是用来显示一段文本,这里说的高级特性主要是一些我们平常不太常用的属性.包括文字阴影.自定义字体.html嵌入多格式.字体加粗.插入图片.这些特性平时开发APP的时候,可能一般使用 ...
- office快速制作简历
毕业的一年是由学校向社会转变的一年,面临着人生的一个重大转折--找工作.在如今信息爆炸的时代,纵使力拔山兮气盖世也难免会被遗落芳草之中而不得一展宏图.对未来的憧憬,对美好生活的向往,或多或少你需要一份 ...
- cglib源码分析--转
原文地址:http://www.iteye.com/topic/799827 背景 前段时间在工作中,包括一些代码阅读过程中,spring aop经常性的会看到cglib中的相关内容,包括BeanCo ...
- Pillow实现图片对比
在编写Web自动化测试用例的时候,如何写断言使新手不解,严格意义上来讲,没有断言的自动化脚本不能叫测试用例.就像功能测试一样,当测试人员做了一些操作之后必然会判断实际结果是否等于预期结果,只不过,这个 ...
- iOS_UIImage_给图片添加水印
github地址: https://github.com/mancongiOS/UIImage.git UIImage的Category UIImage+ImageWaterPrint.h #impo ...