StackOverflow の質問への回答で、次のようにストリームを val として作成しました。
val s:Stream[Int] = 1 #:: s.map(_*2)
Scala Kata は (Eclipse の Scala ワークシートと同様に) 「前方参照が値の定義を超えている」と文句を言うので、 valの代わりにdefを使用する必要があると誰かが私に言いました。
ただし、Stream ドキュメントの例では val を使用しています。どちらが正しいですか?
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 を使用した場合に注意してください。
しかし、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
は、そのようなストリームを生成する簡単な方法です。値を作成する方法を伝えるだけで、ストリームが作成されます。