【问题标题】:Lazy val to implement lazy lists in ScalaLazy val 在 Scala 中实现惰性列表
【发布时间】:2014-04-12 13:56:39
【问题描述】:

我正在尝试通过实现我自己的惰性列表版本来学习如何使用 Scala 中的内置惰性:

object LazyList {

  def empty[A] : LazyList[A] = new LazyList[A] {
    lazy val uncons = None
  }

  def cons[A](h : => A, t : => LazyList[A]) : LazyList[A] = new LazyList[A] {
    lazy val uncons = Some( (h,t) )
  }

  def from(s : Int) : LazyList[Int] = new LazyList[Int] {
    lazy val uncons = Some( (s,from(s + 1)) )
  }
}

trait LazyList[A] {

  import LazyList._

  def uncons : Option[(A,LazyList[A])]

  def fmap[B](f : A => B) : LazyList[B] = uncons match {
    case None          => empty
    case Some( (h,t) ) => cons(f(h),t.fmap(f))
  }

  def take(i : Int) : LazyList[A] = uncons match {
    case None          => empty
    case Some( (h,t) ) => if (i <= 0) empty else cons(h,t.take(i - 1))
  }

  override def toString : String = uncons match {
    case None          => "[]"
    case Some( (h,t) ) => "[" ++ h.toString ++ ",..]"
  }
}

这似乎可行,例如,我可以将 { _ + 2} 映射到无限列表:

> LazyList from 1 fmap { _ + 2 }
res1: LazyList[Int] = [2,..]

我决定实现一些我通常使用的函数,如droptake 等,除了inits 之外,我已经能够实现它们。我对inits 的实现是:

  def inits : LazyList[LazyList[A]] = uncons match {
    case None          => empty
    case Some( (h,t) ) => cons(empty,t.inits.fmap(cons(h,_)))
  }

问题是由于某种原因它不适用于无限列表。例如,我不能写:

  > LazyList from 1 inits

因为它永远运行。问题似乎是在t.inits 之后的fmap,由于某种原因,它打破了懒惰(如果我删除fmap,这是错误的但懒惰)。为什么fmap 强制执行严格性,并且考虑到我的类型LazyList,如何实现inits 使其适用于无限列表?

【问题讨论】:

    标签: scala lazy-evaluation


    【解决方案1】:

    fmapinits 在调用时都会剥离一个实际(非惰性)元素;他们都是uncons。由于它们相互调用,因此链永远不会在无限 LazyList 处终止。

    具体来说,请注意uncons 返回的不是=&gt; LazyList,而是实际的LazyList,所以当你调用时

    Some( (h,t) )
    

    评估t。如果t 的求值调用uncons,它也会求值并且你将进入堆栈溢出竞赛。在这里注意到它很棘手,因为它是双重递归。

    您需要将其中一个剥离 份。一种方法是使 uncons 元组的第二个参数变得惰性(明确地,将其改为 Function0):

    object LazyList {
      def empty[A]: LazyList[A] = new LazyList[A] {
        lazy val uncons = None
      }
    
      def cons[A](h: => A, t: => LazyList[A]) : LazyList[A] = new LazyList[A] {
        lazy val uncons = Some( (h,() => t) )
      }
    
      def from(s: Int): LazyList[Int] = new LazyList[Int] {
        lazy val uncons = Some( (s,() => from(s + 1)) )
      }
    }
    
    trait LazyList[A] {
      import LazyList._
    
      def uncons: Option[(A,() => LazyList[A])]
    
      def fmap[B](f: A => B): LazyList[B] = uncons match {
        case None          => empty
        case Some( (h,t) ) => cons(f(h),t().fmap(f))
      }
    
      def take(i: Int): LazyList[A] = uncons match {
        case None          => empty
        case Some( (h,t) ) => if (i <= 0) empty else cons(h,t().take(i - 1))
      }
    
      override def toString: String = uncons match {
        case None          => "[]"
        case Some( (h,t) ) => "[" ++ h.toString ++ ",..]"
      }
    }
    

    然后你的实现工作:

      def inits: LazyList[LazyList[A]] = uncons match {
        case None          => empty
        case Some( (h,t) ) => cons(empty,t().inits.fmap(cons(h,_)))
      }
    

    最好有一个内部的 uncons 来做这件事,一个向外的 uncons 来为你应用尾巴。

    【讨论】:

    • 对我来说还是很奇怪:cons 需要t: =&gt; LazyList[A],所以它是懒惰的,而 uncons 是lazy val,那么为什么cons 评估尾部?例如,如果我写cons(f(h),t.fmap(f))f(h)f.fmap(f) 都应该是惰性的,并且在需要时才进行评估。为什么需要它们?
    • @mariop - Some( (h,t) ) 并不懒惰。您在那里评估t。我在上面编辑了我的答案,试图更加强调这一点。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-11
    • 2014-10-07
    • 2020-12-28
    • 2012-04-27
    • 2011-05-29
    • 1970-01-01
    相关资源
    最近更新 更多