Scala 惰性评估解释

Scala 惰性评估解释,scala,Scala,Update:用两个构造函数上的用法更新了代码,基本上我混淆的是,Cons和Stream.Cons之间的区别是什么。另外,这个例子来自《Scala中的函数式编程》一书的第5章 那么,有人能向我解释一下为什么下面的惰性评估没有按预期的那样工作吗?我的Scala工作表的输出是 One res0: Int = 1 One res1: Int = 2 One res2: Int = 3 One res3: Int = 4 One res4: Int = 5 One res5: Int = 6 清楚这不

Update:用两个构造函数上的用法更新了代码,基本上我混淆的是,
Cons
Stream.Cons
之间的区别是什么。另外,这个例子来自《Scala中的函数式编程》一书的第5章

那么,有人能向我解释一下为什么下面的惰性评估没有按预期的那样工作吗?我的Scala工作表的输出是

One
res0: Int = 1
One
res1: Int = 2
One
res2: Int = 3
One
res3: Int = 4
One
res4: Int = 5
One
res5: Int = 6
清楚这不是预期的输出,因为由于延迟计算,
One
应该只打印一次,
i
应该只增加一次,但情况似乎并非如此。我错过了一些东西,但看不见,有没有新的一双眼睛可以帮忙

sealed trait Stream[+A] {
  def toList: List[A] = {
    @annotation.tailrec
    def go(s: Stream[A], acc: List[A]): List[A] = s match {
      case Cons(h,t) => go(t(), h() :: acc)
      case _ => acc
    }
    go(this, List()).reverse
  }
}
case object Empty extends Stream[Nothing]
case class Cons[+A](h: () => A, t: () => Stream[A]) extends Stream[A]

object Stream {
  def cons[A](hd: => A, tl: => Stream[A]): Stream[A] = {
    lazy val head = hd
    lazy val tail = tl
    Cons(() => head, () => tail)
  }

  def empty[A]: Stream[A] = Empty

  def apply[A](as: A*): Stream[A] =
    if (as.isEmpty) empty
    else cons(as.head, apply(as.tail: _*))
}

var i = 0
val nonLazy = Cons(
  () => { println("One"); i+=1; i }, 
  () => Cons(
    () => { println("Two"); i+=2; i }, 
    () => Empty))
nonLazy.h
nonLazy.h
nonLazy.h

var i = 0
val lazy = Stream.cons(
  () => { println("One"); i+=1; i }, 
  Stream.cons(
    () => { println("Two"); i+=2; i }, 
    Empty)).toList
lazy.head
lazy.head
lazy.head
lazy.head
lazy.head
lazy.head

我认为您忘记了什么是
hd
——即返回Int的
函数0
,或者使用缩写,
()=>Int

当你称之为:

stream.head()

它的作用是:

  • 检索
    stream.head
    (这是一个函数,因此有一个可以调用的
    apply()
    方法,该方法将打印“一”,将
    i
    增加两倍,然后返回
    i
  • 执行该函数,因为Scala使调用前面提到的
    apply()
    方法变得非常方便
  • 如果不希望执行该函数,请尝试以下操作:

    stream.head
    stream.head
    stream.head
    stream.head
    stream.head
    stream.head
    
    这将返回:

    res0: () => Int = <function0>
    res1: () => Int = <function0>
    res2: () => Int = <function0>
    res3: () => Int = <function0>
    res4: () => Int = <function0>
    res5: () => Int = <function0>
    
    res0:()=>Int=
    res1:()=>Int=
    res2:()=>Int=
    res3:()=>Int=
    res4:()=>Int=
    res5:()=>Int=
    
    val
    确实是懒惰的-每次都是相同的函数-但是你要执行它6次

    lazy val head = hd
    
    由于您没有指定
    头的类型
    ,它将是
    ()=>Int
    ,并且每次获取头时,
    hd
    正在评估,
    i
    不断增加

    我认为您需要计算
    hd
    并将值存储在
    Stream.cons
    函数中,因此需要显式指定类型:

    lazy val head: A = hd
    

    tail
    变量也是如此。

    如果要调用
    Stream.cons(()=>{println(“One”);i+=1;i},Stream.cons(()=>{println(“Two”);i+=2;i},Empty))
    ,则需要修复其类型:

    def cons[A](hd: () => A, tl: () => Stream[A]): Stream[A] = {
      lazy val head = hd()
      lazy val tail = tl()
      Cons(() => head, () => tail)
    }
    
    要访问它,您需要

    val stream = Stream.cons(()=>{println("One"); i+=1; i}, Stream.cons(()=>{println("Two"); i+=2; i}, Stream.Empty)) // won't print anything
    stream.h() // will print "One" and return 1
    stream.h() // will return 1 without printing anything
    stream.h() // will return 1 without printing anything
    

    使用
    toList
    将阻止你真正看到懒惰。

    我想他可能是这个意思,但如果是这样的话,那么
    case-class-Cons
    的类型声明就完全错误了(尽管这可能是因为它们是无意中推断出来的,而不是他的意思).等等,它有什么帮助?
    head
    已经是
    A
    ,只有
    A
    ()=>Int
    如果这样调用。我会假设
    System.cons({println(“一”);println(I);I+=1;I},…
    相反,所以
    A
    Int
    。声明
    head:A
    没有帮助,当我尝试时没有。事实上,Victor说的是正确的,指定type to head并没有做我想做的事,就是避免函数运行不止一次,在您指定的情况下,发生的一切就是我得到sam如果我理解正确的话,在没有运行的情况下引用相同的函数。@MrX在这种情况下,您需要修改
    Cons
    case类以使用
    A
    Stream[A]
    ,而不是
    ()=>
    ,返回这些值。不过,我在这里遗漏了一些东西,如果我这样做,我就失去了拥有两个构造函数的范围,Cons和Cons,Cons不应该使用延迟求值,每次都应该求值函数,而Cons应该使用延迟求值,只要我问你这个问题,就运行函数-你想让我这样做吗因此,它将实际函数存储在
    hd
    tl
    中,并因此存储在
    head
    tail
    中,或者您希望它们属于
    A
    流[A]类型吗
    ?我明白你的意思,但我试图通过惰性计算将高阶函数转换为另一个高阶函数,该函数只返回第一次运行时的结果。
    ()=>{println(“One”);println(I);I+=1;I}
    转换为
    ()=>2
    由于它是惰性的,所以只要在
    cons
    中有
    =>A
    就可以运行,我假设您想要传递类型为
    A
    的表达式,而不是函数,比如
    System.cons({println(“One”);println(I);I+=1;I},
    。那么您就不需要
    ()
    ,只要
    stream.head
    ,它就会像预期的那样工作。我明白你所说的,Victor,它确实按预期工作,但这不是违背了它的全部目的,就像你自己应用了函数,并将函数的结果返回到流中。同样,根据你的建议,我可以删除
    的head
    lazy val tail
    的效果与
    Cons(()=>hd,()=>tl)
    @VictorMoroz这应该是一个答案(特别是因为现有的答案是错误的)。@MrX否。试试看:删除lazy val将导致每次调用
    lazy.h()时都调用
    println
    。这与@VictorMoroz answer一起解释了这一点。我认为他的解决方案也很有效。你可以用同样的方式看到它的懒惰。