This question is targeted to Vavr (Java's FP library), but could probably apply to Scala or other FP languages that can compare to Java.
What's the difference between Vavr foldLeft
:
<U> U foldLeft(U zero,
BiFunction<? super U, ? super T, ? extends U> combiner)
and Java's collect
:
<R> R collect(Supplier<R> supplier,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner)
It seems both can achieve the same purpose: Transform a collection (say Stream<T>
) by traversing it and accumulating a result into a new type U
(or R
) while traversing.
At first sight, signatures differ in #arguments, and Java collect
seems to be intended to be run in parallel chunks.
Which are the actual differences conceptually? Vavr foldLeft
seems easier to use.
Very dumb example just to illustrate:
Vavr foldLeft
:
// result = "HelloFunctionalWorld"
String result = Seq("Hello", "Functional", "World")
.foldLeft(new StringBuilder(), (acc, word) -> acc.append(word))
.toString();
Java collect
:
// result = "HelloFunctionalWorld"
final String result = Arrays.asList("Hello", "Functional", "World").stream()
.collect(
StringBuilder::new,
(acc, word) -> acc.append(word),
StringBuilder::append
)
.toString();
Which are the actual differences conceptually?
As the javadoc of collect(Supplier<R> supplier, BiConsumer<R,? super T> accumulator, BiConsumer<R,R> combiner)
says:
Performs a mutable reduction operation on the elements of this stream. A mutable reduction is one in which the reduced value is a mutable result container, such as an
ArrayList
, and elements are incorporated by updating the state of the result rather than by replacing the result. This produces a result equivalent to:R result = supplier.get(); for (T element : this stream) accumulator.accept(result, element); return result;
The key phrase here is mutable, since Vavr doesn't do anything mutable, being a functional library.
The example in the question using StringBuilder
is actually a violation of the functional principles. Why use Vavr if you don't intend to follow the functional paradigms?
The Java Stream equivalent of foldLeft()
is reduce()
, and you're correct, the 3rd parameter is to support parallel processing:
// Vavr
<U> U foldLeft(U zero,
BiFunction<? super U,? super T,? extends U> combine)
// Java Stream
<U> U reduce(U identity,
BiFunction<U,? super T,U> accumulator,
BinaryOperator<U> combiner)
If the result type is the same as the stream element type, you would be using the following alternatives, which don't need a 3rd parameter for parallel processing support:
// Vavr
T fold(T zero,
BiFunction<? super T,? super T,? extends T> combine)
// Java Stream
T reduce(T identity,
BinaryOperator<T> accumulator)
UPDATE Comparison of the methods:
Vavr
T fold(T zero, BiFunction<? super T,? super T,? extends T> combine)
Applies combine
in arbitrary order, so it must be an associative function.
Equivalent to reduce(identity, accumulator)
.
<U> U foldLeft(U zero, BiFunction<? super U,? super T,? extends U> combine)
Applies values from left-to-right.
Equivalent to reduce(identity, accumulator, combiner)
in sequential mode, where combiner
is unused. Parallel mode supported with a valid combiner
function.
<U> U foldRight(U zero, java.util.function.BiFunction<? super T,? super U,? extends U> combine)
Applies values from right-to-left.
No Java Stream equivalent.
Java Stream
Optional<T> reduce(BinaryOperator<T> accumulator)
Applies accumulator
in arbitrary order, so it must be an associative function. E.g. streaming 1,2,3,4
with +
could be processed as ((1+2)+3)+4
(sequential mode), or (1+2)+(3+4)
, 1+((2+3)+4)
or any order combination (parallel mode).
No Vavr equivalent.
T reduce(T identity, BinaryOperator<T> accumulator)
Applies accumulator
in arbitrary order, so it must be an associative function. E.g. streaming 1,2,3,4
with 0
and +
could be processed as (((0+1)+2)+3)+4
(sequential mode), or ((0+1)+2) + ((0+3)+4)
or any order combination (parallel mode).
Equivalent to fold(zero, combine)
.
<U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)
Applies values in order, with combiner
providing support for parallel processing.
Sequential mode is equivalent to foldLeft(zero, combine)`.
<R> R collect(Supplier<R> supplier, BiConsumer<R,? super T> accumulator, BiConsumer<R,R> combiner)
In sequential mode, supplier
is used to create a mutable result container, and values are then applied in order. In parallel mode, multiple mutable result containers are created and filled with values, and are then merged using the combiner
function.
No Vavr equivalent.