Eran's answer descritte le differenze tra i due-arg e le versioni a tre-Arg di reduce
in quanto il primo riduce Stream<T>
-T
mentre il quest'ultimo riduce Stream<T>
a U
. Tuttavia, non ha effettivamente spiegato la necessità della funzione combinatore aggiuntiva quando si riduce Stream<T>
a U
.
Uno dei principi di progettazione dell'API di Streams è che l'API non deve differire tra flussi sequenziali e paralleli o, in altri termini, una particolare API non dovrebbe impedire il corretto svolgimento di uno stream in sequenza o in parallelo. Se i tuoi lambda hanno le proprietà giuste (associative, non interferenti, ecc.), Un flusso eseguito sequenzialmente o in parallelo dovrebbe dare gli stessi risultati.
T reduce(I, (T, T) -> T)
L'implementazione sequenziale è semplice:
di prima prendere in considerazione la versione a due-arg della riduzione Let. Il valore identitario I
viene "accumulato" con l'elemento zeroth stream per fornire un risultato. Questo risultato viene accumulato con il primo elemento stream per fornire un altro risultato, che a sua volta viene accumulato con il secondo elemento stream e così via. Dopo che l'ultimo elemento è stato accumulato, viene restituito il risultato finale.
L'implementazione parallela inizia dividendo il flusso in segmenti. Ogni segmento viene elaborato dal proprio thread nel modo sequenziale descritto sopra. Ora, se abbiamo thread N, abbiamo N risultati intermedi. Questi devono essere ridotti a un solo risultato. Poiché ogni risultato intermedio è di tipo T, e ne abbiamo diversi, possiamo usare la stessa funzione di accumulatore per ridurre quei risultati intermedi N a un unico risultato.
Ora consideriamo un'ipotetica operazione di riduzione a due arg che riduce Stream<T>
a U
. In altre lingue questa operazione viene chiamata "fold" o "fold-left" quindi è quello che chiamerò qui. Nota questo non esiste in Java.
U foldLeft(I, (U, T) -> U)
(Si noti che il valore di identità I
è di tipo U.)
La versione sequenziale di foldLeft
è proprio come la versione sequenziale di reduce
tranne che i valori intermedi sono di tipo U invece di tipo T Ma è altrimenti lo stesso. (Un'ipotetica operazione foldRight
sarebbe simile eccetto che le operazioni verrebbero eseguite da destra a sinistra anziché da sinistra a destra.)
Consideriamo ora la versione parallela di foldLeft
. Iniziamo dividendo il flusso in segmenti. Possiamo quindi fare in modo che ciascuno dei thread N riduca i valori T nel suo segmento in N valori intermedi di tipo U. Ora che cosa? Come possiamo ottenere da N i valori di tipo U fino a un singolo risultato di tipo U?
Nei manca è un'altra funzione che combina molteplici risultati intermedi di tipo U in un unico risultato di tipo U. Se abbiamo una funzione che combina due valori U in uno, che è sufficiente a ridurre qualsiasi numero di valori giù a uno - proprio come la riduzione originale sopra.Così, l'operazione di riduzione che dà un risultato di tipo diverso ha bisogno di due funzioni:
U reduce(I, (U, T) -> U, (U, U) -> U)
Oppure, utilizzando la sintassi Java:
<U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)
In sintesi, per fare parallela riduzione a un diverso tipo di risultato, abbiamo bisogno di due funzioni: una che accumula elementi T in valori U intermedi e un secondo che combina i valori intermedi U in un singolo risultato U. Se non stiamo cambiando i tipi, risulta che la funzione dell'accumulatore è la stessa della funzione combinatore. Ecco perché la riduzione allo stesso tipo ha solo la funzione di accumulatore e la riduzione ad un tipo diverso richiede funzioni separate di accumulatore e combinatore.
Infine, Java non fornisce operazioni foldLeft
e foldRight
perché implicano un particolare ordine di operazioni che è intrinsecamente sequenziale. Ciò si scontra con il principio di progettazione sopra riportato di fornire API che supportano allo stesso modo operazioni sequenziali e parallele.
domanda correlata: https://stackoverflow.com/questions/24202473/does-a-sequential-stream-in-java-8-use-the-combiner-parameter-on-calling-collect – nosid
aha, è per flussi paralleli ... Io chiamo astrazione leaky! – Andy