在回答StackOverflow问题时,我创建了一个Stream作为val,如下所示:
val s:Stream[Int] = 1 #:: s.map(_*2)
并且有人告诉我应该使用 def 而不是 val ,因为Scala Kata抱怨(与Eclipse中的Scala工作表一样)“前向引用扩展到定义价值s。“
但Stream文档中的示例使用val。哪一个是对的?
答案 0 :(得分:22)
只要变量是类的字段而不是局部变量,Scalac和REPL就可以使用该代码(使用val)。你可以使变量懒惰以满足Scala Kata,但是你通常不希望在真实程序中以这种方式使用def(即,根据自身来定义Stream)。如果这样做,每次调用方法时都会创建一个新的Stream,因此以前的计算结果(保存在Stream中)永远不会被重用。如果您使用来自此类Stream的许多值,性能将非常糟糕,最终您将耗尽内存。
此程序演示了以这种方式使用def的问题:
// Show the difference between the use of val and def with Streams.
object StreamTest extends App {
def sum( p:(Int,Int) ) = { println( "sum " + p ); p._1 + p._2 }
val fibs1: Stream[Int] = 0 #:: 1 #:: ( fibs1 zip fibs1.tail map sum )
def fibs2: Stream[Int] = 0 #:: 1 #:: ( fibs2 zip fibs2.tail map sum )
println("========== VAL ============")
println( "----- Take 4:" ); fibs1 take 4 foreach println
println( "----- Take 5:" ); fibs1 take 5 foreach println
println("========== DEF ============")
println( "----- Take 4:" ); fibs2 take 4 foreach println
println( "----- Take 5:" ); fibs2 take 5 foreach println
}
这是输出:
========== VAL ============
----- Take 4:
0
1
sum (0,1)
1
sum (1,1)
2
----- Take 5:
0
1
1
2
sum (1,2)
3
========== DEF ============
----- Take 4:
0
1
sum (0,1)
1
sum (0,1)
sum (1,1)
2
----- Take 5:
0
1
sum (0,1)
1
sum (0,1)
sum (1,1)
2
sum (0,1)
sum (0,1)
sum (1,1)
sum (1,2)
3
请注意,当我们使用val:
时但是当我们使用def时,这些都不是真的。 Stream的每次使用,包括它自己的递归,都是从头开始使用新的Stream。由于产生第N个值需要我们首先产生N-1和N-2的值,每个值必须产生它自己的两个前辈等等,产生值所需的sum()调用的数量增长很像Fibonacci序列本身:0,0,1,2,4,7,12,20,33 ......由于所有这些Streams同时在堆上,我们很快就会耗尽内存。 / p>
因此,考虑到性能和内存不佳问题,您通常不希望在创建Stream时使用def。
但可能实际上 每次都想要一个新流。假设您想要一个随机整数流,并且每次访问Stream时都需要新的整数,而不是先前计算的整数的重放。而那些以前计算过的值,因为你不想重用它们,会不必要地占用堆上的空间。在这种情况下,使用def是有意义的,这样你每次都可以获得一个新的Stream,而不是坚持使用它,这样它就可以被垃圾收集了:
scala> val randInts = Stream.continually( util.Random.nextInt(100) )
randInts: scala.collection.immutable.Stream[Int] = Stream(1, ?)
scala> ( randInts take 1000 ).sum
res92: Int = 51535
scala> ( randInts take 1000 ).sum
res93: Int = 51535 <== same answer as before, from saved values
scala> def randInts = Stream.continually( util.Random.nextInt(100) )
randInts: scala.collection.immutable.Stream[Int]
scala> ( randInts take 1000 ).sum
res94: Int = 49714
scala> ( randInts take 1000 ).sum
res95: Int = 48442 <== different Stream, so new answer
使randInts成为一种方法会导致我们每次都获得一个新的Stream,因此我们获得了新的值,并且可以收集Stream。
请注意,在这里使用def只是有意义的,因为新值不依赖于旧值,因此randInts本身并未定义。 Stream.continually
是生成此类Streams的简单方法:您只需告诉它如何创建一个值,它就会为您创建一个Stream。