Scala 惰性评估解释
Update:用两个构造函数上的用法更新了代码,基本上我混淆的是,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 清楚这不
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
)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一起解释了这一点。我认为他的解决方案也很有效。你可以用同样的方式看到它的懒惰。