定义流时我应该使用val还是def?

时间:2012-11-04 08:43:54

标签: scala stream

在回答StackOverflow问题时,我创建了一个Stream作为val,如下所示:

val s:Stream[Int] = 1 #:: s.map(_*2)

并且有人告诉我应该使用 def 而不是 val ,因为Scala Kata抱怨(与Eclipse中的Scala工作表一样)“前向引用扩展到定义价值s。“

但Stream文档中的示例使用val。哪一个是对的?

1 个答案:

答案 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:

  • “take 5”没有重新计算“take 4”计算出的值。
  • 计算“take 4”中的第4个值不会导致重新计算第3个值。

但是当我们使用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。

相关问题