Eranの回答は、reduce
前者がに減少Stream<T>
するのT
に対し、後者はに減少Stream<T>
するという2引数バージョンと3引数バージョンの違いを説明していU
ます。ただし、に削減Stream<T>
する場合の追加のコンバイナ機能の必要性は実際には説明されていませんU
。
Streams APIの設計原則の1つは、APIがシーケンシャルストリームとパラレルストリームで異なっていてはならない、言い換えると、特定のAPIがストリームのシーケンシャルまたはパラレルの正常な実行を妨げないことです。ラムダに適切なプロパティ(連想、非干渉など)がある場合、順次または並列に実行されるストリームは同じ結果をもたらすはずです。
まず、2引数バージョンのリダクションについて考えてみましょう。
T reduce(I, (T, T) -> T)
順次実装は簡単です。ID値I
は、結果を与えるために、0番目のストリーム要素で「累積」されます。この結果は、最初のストリーム要素と累積されて別の結果が得られ、次に2番目のストリーム要素と累積されます。最後の要素が蓄積された後、最終結果が返されます。
並列実装は、ストリームをセグメントに分割することから始まります。各セグメントは、前述の順次方式で独自のスレッドによって処理されます。ここで、N個のスレッドがある場合、N個の中間結果があります。これらは、1つの結果に減らす必要があります。各中間結果はタイプTであり、いくつかあるため、同じアキュムレーター関数を使用して、それらのN個の中間結果を単一の結果に減らすことができます。
今度は、削減仮想の2-argを低減動作考えるStream<T>
にはU
。他の言語では、これは「フォールド」または「フォールドレフト」操作と呼ばれるため、ここではそれを呼び出します。これはJavaには存在しないことに注意してください。
U foldLeft(I, (U, T) -> U)
(ID値I
はタイプUであることに注意してください。)
の順次バージョンは、中間値がタイプTではなくタイプUであることfoldLeft
をreduce
除いて、順次バージョンのと同じですが、それ以外は同じです。(仮想的なfoldRight
操作は、操作が左から右ではなく右から左に実行されることを除いて、類似しています。)
次に、の並列バージョンを考えfoldLeft
ます。ストリームをセグメントに分割することから始めましょう。次に、N個のスレッドのそれぞれに、そのセグメントのT値をU型のN個の中間値に減らすことができます。U型のN個の値からU型の単一の結果にどのようにして到達するのでしょうか?
不足しているのは、タイプUの複数の中間結果をタイプUの単一の結果に結合する別の関数です。2つのU値を1つに結合する関数がある場合、これは、任意の数の値を1に減らすのに十分です-同様に上記の元の削減。したがって、異なるタイプの結果を与えるリダクション操作には、2つの関数が必要です。
U reduce(I, (U, T) -> U, (U, U) -> U)
または、Java構文を使用します。
<U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)
要約すると、異なる結果タイプに並列削減を行うには、2つの関数が必要です。1つはT要素を中間U値に累積し、もう1つは中間U値を単一のU結果に結合します。タイプを切り替えない場合、アキュムレータ関数はコンバイナ関数と同じであることがわかります。そのため、同じタイプへの削減にはアキュムレータ関数のみがあり、別のタイプへの削減には、別々のアキュムレータおよびコンバイナ関数が必要です。
最後に、Javaが提供していないfoldLeft
とfoldRight
、彼らは本質的にシーケンシャルである操作の特定の順序を暗示するので操作。これは、シーケンシャル操作とパラレル操作を同等にサポートするAPIを提供するという前述の設計原則と衝突します。