定义流时应该使用 val 还是 def ? [英] Should I use val or def when defining a Stream?

查看:47
本文介绍了定义流时应该使用 val 还是 def ?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

In an answer to a StackOverflow question I created a Stream as a val, like so:

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

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

and somebody told me that def should be used instead of val because Scala Kata complains (as does the Scala Worksheet in Eclipse) that a "forward reference extends over definition of value s."

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

But the examples in the Stream docs use val. Which one is right?

推荐答案

Scalac 和 REPL 可以使用该代码(使用 val),只要变量是类的字段而不是局部变量.您可以使变量惰性以满足 Scala Kata 的要求,但您通常不希望在实际程序中以这种方式使用 def(即,就其本身而言 def 是一个 Stream).如果这样做,每次调用该方法时都会创建一个新的 Stream,因此之前计算的结果(保存在 Stream 中)永远无法重用.如果你使用来自这样一个 Stream 的很多值,性能会很糟糕,最终你会耗尽内存.

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.

这个程序演示了以这种方式使用 def 的问题:

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
}

输出如下:

========== 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 时:

Notice that when we used 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, ....而且由于所有这些流同时在堆上,我们很快就会耗尽内存.

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.

因此,由于性能和内存问题不佳,您通常不想在创建流时使用 def.

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

但可能您实际上确实每次都想要一个新的 Stream.假设您想要一个随机整数的 Stream,并且每次访问该 Stream 时,您都需要新的整数,而不是之前计算的整数的重放.而那些先前计算的值,由于您不想重用它们,会不必要地占用堆上的空间.在这种情况下,使用 def 是有意义的,这样您每次都会获得一个新的 Stream 并且不保留它,以便它可以被垃圾收集:

But it might be that you actually do want a new Stream each time. Let's say that you want a Stream of random integers, and each time you access the Stream you want new integers, not a replay of previously computed integers. And those previously computed values, since you don't want to reuse them, would take up space on the heap needlessly. In that case it makes sense to use def so that you get a new Stream each time and don't hold on to it, so that it can be garbage-collected:

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.

Making randInts a method causes us to get a new Stream each time, so we get new values, and the Stream can be collected.

请注意,这里只使用 def 才有意义,因为新值不依赖于旧值,因此 randInts 不是根据其本身定义的.Stream.continually 是生成此类流的一种简单方法:您只需告诉它如何生成值,它就会为您生成一个流.

Notice that it only makes sense to use def here because new values don't depend on old values, so randInts is not defined in terms of itself. Stream.continually is an easy way to produce such Streams: you just tell it how to make a value and it makes a Stream for you.

这篇关于定义流时应该使用 val 还是 def ?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆