2013-04-23 58 views
2

这个问题是与此相关的其他question但降低到一个更简单的情况:scalaz蹦床和IO

我认为以下进口:

import scalaz._, Scalaz._ 
import Free._, effect._ 

我有以下发电机:

val fromOneIO:() => IO[Int] = { 
    var i = 0;() => { i += 1; IO(i) } 
} 
val fromOne:() => Int = { 
    var i = 0;() => { i += 1; i } 
} 

以及以下非尾递归定义:

def rec(i: Int): Int = { 
    if (i == 0) { 
    fromOne() 
    } else { 
    rec(i - 1) + fromOne() 
    } 
} 
def rec1(i: Int): Trampoline[Int] = { 
    if (i == 0) { 
    Return(fromOne()) 
    } else { 
    suspend { 
     for { 
     a <- rec1(i - 1) 
     b <- Return(fromOne()): Trampoline[Int] 
     } yield a + b 
    } 
    } 
} 
def recio(i: Int): Trampoline[IO[Int]] = { 
    if (i == 0) { 
    Return(fromOneIO()) 
    } else { 
    suspend { 
     for { 
     ioa <- recio(i - 1) 
     iob <- Return(fromOneIO()): Trampoline[IO[Int]] 
     } yield { 
     for (a <- ioa; b <- iob) yield a + b 
     } 
    } 
    } 
} 

结果是:

rec(100) // overflows for arg 10000 
rec1(10000).run // works 
recio(10000).run.unsafePerformIO() //overflows 

如何管理,使IO地图/ flatMap一并trampolined?看起来我有其他嵌套堆栈创建在第二个理解。我是否需要编写一个TrampolineT将使用unsafePerformIO并将提取的io值重新封装为暂停?

+0

对于这个特定的例子,'IO [A:Monoid]的'Monoid'实例似乎正在工作。也就是说,只要将你的'yield'语句改为'ioa | + | iob'。尽管如此,我没有什么可以推荐的。 – folone 2013-04-23 14:36:08

+0

实际上,问题似乎出现在'IO'的'Monad'实例中。如果你使用它的'Applicative'实例,它似乎工作得很好。 ''yield(ioa | @ | iob){_ + _}' – folone 2013-04-23 14:42:15

+0

你希望'IO'本身被蹦出来,而不是构建一个巨大的嵌套'IO'动作链。你的代码允许你建立一个'IO'动作而不会溢出你的堆栈,但是当你运行'IO'动作时,你构建它的方式会导致堆栈溢出。 – 2013-04-23 14:57:59

回答

3

因此IO(_)已经是trampolined,它的价值。除了从folone的建议,我能够通过改变第二避免溢出为这样的:

val s = for (a <- ioa; b <- iob) yield a + b 
val s1 = s.unsafePerformIO() 
IO(s1) 

或者这样:

IO(for (a <- ioa; b <- iob) yield a + b).flatMap(identity) 

而且IO(_)需要通过名称参数,所以IO(expr)val e = expr; IO(e)行为不一样。这会溢出!

val s = for (a <- ioa; b <- iob) yield a + b 
IO(s.unsafePerformIO()) 

所以,虽然它不是一个非常令人满意的答案似乎使用应用性在可能或包装在另一个IO和扁平化是解决方法吹堆栈。