object test2 { //a naive IO monad sealed trait IO[A] { self => def run: A def map[B](f: A => B): IO[B] = new IO[B] { def run = f(self.run) } def flatMap[B](f: A => IO[B]): IO[B] = { println("calling IO.flatMap") new IO[B] { def run = { println("calling run from flatMap result") f(self.run).run } } } } object IO { def unit[A](a: => A): IO[A] = new IO[A] { def run = a } def apply[A](a: => A): IO[A] = unit(a) // Syntax for IO { .. } } //composer in question def forever[A,B](a: IO[A]): IO[B] = { lazy val t: IO[B] = a flatMap (_ => t) t } def PrintLine(msg: String) = IO { println(msg) } def say = forever(PrintLine("Still Going..")).run }
在堆栈溢出之前,test2.say将打印数千个“Still Going”.但我不确切知道这是怎么发生的.
输出如下所示:
阶> test2.say
只调用IO.flatMap //一次
从flatMap结果调用run
仍在继续..
从flatMap结果调用run
仍在继续..
… //重复直到堆栈溢出
当函数永远返回时,懒惰的val是否被完全计算(缓存)?
并且,flatMap方法似乎只被调用一次(我添加了print语句),它反对永远的递归定义.为什么?
===========
我觉得有趣的另一件事是永远[A,B]中的B型可能是任何东西. Scala实际上可以运行,因为它是不透明的.
我手动尝试永远[单位,双],永远[单位,字符串]等,这一切都有效.这感觉很聪明.
解决方法
I’d like to know when function forever returns,is the lazy val t fully computed (cached)?
是
If so then why need the lazy keyword?
在你的情况下没用.在以下情况下它可能很有用:
def repeat(n: Int): Seq[Int] { lazy val expensive = "some expensive computation" Seq.fill(n)(expensive) // when n == 0,the 'expensive' computation will be skipped // when n > 1,the 'expensive' computation will only be computed once }
The other thing I don’t understand is that the flatMap method seems to
be called only once (I add print statements) which counters the
recursive definition of forever. Why?
在您提供最小,完整和可验证的示例之前无法发表评论,例如@Yuval Itzchakov说
更新时间2017/04/19
好吧,我需要纠正自己:-)在你的情况下,由于递归引用回到自身,所以需要lazy val.
为了解释你的观察,让我们尝试扩展forever(a).run调用:
>永远(a)扩展到
> {lazy val t = flatMap(_ => t)}扩展为
> {lazy val t = new IO [B] {def run()= {… t.run}}
因为t是惰性的,所以2和3中的flatMap和新IO [B]只被调用一次,然后“缓存”以便重用.
在3上调用run()时,你会在t.run上开始递归,从而得到你观察到的结果.
不完全确定您的要求,但永远的非堆栈版本可以实现如下:
def forever[A,B](a: IO[A]): IO[B] = { new IO[B] { @tailrec override def run: B = { a.run run } } }