11

StackOverflow の質問への回答で、次のようにストリームを val として作成しました。

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

Scala Kata は (Eclipse の Scala ワークシートと同様に) 「前方参照が値の定義を超えている」と文句を言うので、 valの代わりにdefを使用する必要があると誰かが私に言いました。

ただし、Stream ドキュメントの例では val を使用しています。どちらが正しいですか?

4

1 に答える 1

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 を使用した場合に注意してください。

  • 「テイク 5」は、「テイク 4」によって計算された値を再計算しませんでした。
  • 「テイク 4」で 4 番目の値を計算しても、3 番目の値が再計算されませんでした。

しかし、def を使用する場合はどちらも当てはまりません。独自の再帰を含む Stream のすべての使用は、新しい Stream でゼロから開始します。N 番目の値を生成するには、最初に N-1 と N-2 の値を生成する必要があり、それぞれが独自の 2 つの先行値を生成する必要があるため、値を生成するために必要な sum() の呼び出しの数は次のように大きくなります。フィボナッチ数列自体: 0, 0, 1, 2, 4, 7, 12, 20, 33, ....そして、これらのストリームはすべて同時にヒープ上にあるため、すぐにメモリ不足になります。

そのため、パフォーマンスとメモリの問題が低いため、通常、ストリームの作成に def を使用することは望ましくありません。

しかし、実際に毎回新しい 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

randInts をメソッドにすると、毎回新しい Stream を取得するため、新しい値を取得し、Stream を収集できます。

新しい値は古い値に依存しないため、ここで def を使用しても意味がないことに注意してください。したがって、randInts はそれ自体では定義されません。 Stream.continuallyは、そのようなストリームを生成する簡単な方法です。値を作成する方法を伝えるだけで、ストリームが作成されます。

于 2012-11-04T08:43:53.627 に答える