14

Scala ではストリームが遅延評価されるシーケンスであることはわかっていますが、ストリームが予想よりも熱心に評価されているように見えるため、ある種の根本的な誤解に苦しんでいると思います。

この例では:

 val initial = Stream(1)
 lazy val bad = Stream(1/0)
 println((initial ++ bad) take 1)

が得られますがjava.lang.ArithmeticException、これはゼロ除算が原因と思われます。badストリームから 1 つの要素のみを要求したため、評価されないことが予想されます。どうしたの?

4

3 に答える 3

21

Streams が遅延しているという事実は、メソッドの引数が熱心に評価されるという事実を変えません。

Stream(1/0)に展開しStream.apply(1/0)ます。言語のセマンティクスでは、メソッドが呼び出される前に引数が評価される必要があります (メソッドはStream.apply名前による呼び出し引数を使用しないため)。そのため1/0、メソッドに引数として渡すために評価を試みStream.applyます。これにより、 ArithmeticException が発生します。 .

ただし、これを機能させる方法はいくつかあります。badとしてすでに宣言しているlazy valので、おそらく最も簡単な方法は、#:::評価の強制を避けるためにも遅延ストリーム連結演算子を使用することです。

val initial = Stream(1)
lazy val bad = Stream(1/0)
println((initial #::: bad) take 1)
// => Stream(1, ?)
于 2012-11-16T05:25:18.263 に答える
21

OK、他の回答にコメントした後、コメントを適切な回答に変えることもできると考えました。

ストリームは確かに遅延型であり、必要に応じて要素を計算するだけです (また、 を使用して、 for の#::ように要素ごとにストリーム要素を構築できます)。例として、以下は例外をスローしません。::List

(1/2) #:: (1/0) #:: Stream.empty

これは、 を適用するとき#::に、テールが名前で渡されるため、熱心に評価するのではなく、必要な場合にのみ評価されるためです (詳細についてはConsWrapper.# ::const.applyおよびクラスConsStream.scala参照してください)。一方、ヘッドは値で渡されます。つまり、何があっても、常に熱心に評価されます (Senthil が述べたように)。これは、次のことを行うと実際に ArithmeticException がスローされることを意味します。

(1/0) #:: Stream.empty

ストリームについて知っておく価値のある落とし穴です。ただし、これはあなたが直面している問題ではありません。

あなたの場合、単一の Stream をインスタンス化する前に算術例外が発生します。を呼び出すStream.applylazy val bad = Stream(1/0)、引数は名前によるパラメーターとして宣言されていないため、熱心に実行されます。Stream.apply実際には vararg パラメータを取り、それらは必ず値渡しされます。また、名前で渡された場合でも、ArithmeticException前述のように Stream のヘッドは常に早期に評価されるため、すぐにトリガーされます。

于 2012-11-16T09:36:08.540 に答える
4

Stream は head を評価し、残りの tail は遅延して評価されます。あなたの例では、両方のストリームにヘッドしかないため、エラーが発生します。

于 2012-11-16T05:28:40.767 に答える