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

Ami*_*ico 10 scala stream

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

val s:Stream[Int] = 1 #:: s.map(_*2)
Run Code Online (Sandbox Code Playgroud)

并且有人告诉我应该使用def而不是val,因为Scala Kata抱怨(正如Eclipse中的Scala工作表一样)"前向引用扩展了对值s的定义".

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

Ami*_*ico 22

Scalac and the REPL are fine with that code (using val) as long as the variable is a field of a class rather than a local variable. You can make the variable lazy to satisfy Scala Kata, but you generally wouldn't want to use def in this way (that is, def a Stream in terms of itself) in a real program. If you do, a new Stream is created each time the method is invoked, so the results of previous computations (which are saved in the Stream) can never be reused. If you use many values from such a Stream, performance will be terrible, and eventually you will run out of memory.

This program demonstrates the problem with using def in this way:

// 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
}
Run Code Online (Sandbox Code Playgroud)

Here is the output:

========== 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
Run Code Online (Sandbox Code Playgroud)

Notice that when we used val:

  • The "take 5" didn't recompute the values computed by the "take 4".
  • Computing the 4th value in the "take 4" didn't cause the 3rd value to be recomputed.

But neither of those is true when we use def. Every use of the Stream, including its own recursion, starts from scratch with a new Stream. Since producing the Nth value requires that we first produce the values for N-1 and N-2, each of which must produce its own two predecessors and so on, the number of calls to sum() required to produce a value grows much like the Fibonacci sequence itself: 0, 0, 1, 2, 4, 7, 12, 20, 33, .... And since all of those Streams are on the heap at the same time, we quickly run out of memory.

So given the poor performance and memory issues, you generally don't want to use def in creating a Stream.

但实际上你可能每次想要一个新的Stream.假设您想要一个随机整数流,并且每次访问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
Run Code Online (Sandbox Code Playgroud)

使randInts成为一种方法会导致我们每次都获得一个新的Stream,因此我们获得了新的值,并且可以收集Stream.

请注意,在此处使用def只是有意义,因为新值不依赖于旧值,因此randInts不是根据自身定义的. Stream.continually生成这样的Streams是一种简单的方法:您只需告诉它如何创建一个值,它就会为您创建一个Stream.