11

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

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

有人告诉我应该使用def而不是val因为 Scala Kata 抱怨(就像 Eclipse 中的 Scala Worksheet 一样)“前向引用扩展了 value s 的定义”。

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

4

1 回答 1

22

只要变量是类的字段而不是局部变量,Scalac 和 REPL 就可以使用该代码(使用 val)。您可以使变量变得惰性以满足 Scala Kata 的要求,但您通常不希望在实际程序中以这种方式使用 def(即,就其本身而言 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() 调用次数的增长很像斐波那契数列本身:0, 0, 1, 2, 4, 7, 12, 20, 33, .... 由于所有这些 Streams 同时在堆上,我们很快就会耗尽内存。

因此,鉴于性能不佳和内存问题,您通常不想在创建 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。

于 2012-11-04T08:43:53.627 回答