Folds, traversals, and sequencing
A fold is the abstraction underneath Stream.reduce: walk a collection, accumulate a result. The algebra from Part 12 supplies the vocabulary — a fold that combines with a monoid needs no seed value, and a fold whose combine is associative can be safely regrouped or parallelized.
The Foldable utility
Java cannot express “any foldable structure” without higher-kinded types, so Foldable is honestly a utility class over Iterable/List — the abstractions are real even if the typeclass is not:
public final class Foldable {
public static <A, B> B foldLeft(Iterable<A> values, B initial, BiFunction<B, A, B> folder) { B accumulated = initial; for (A value : values) { accumulated = folder.apply(accumulated, value); } return accumulated; }
public static <A, M> M foldMap(Iterable<A> values, Monoid<M> monoid, Fn1<? super A, ? extends M> mapper) { M accumulated = monoid.empty(); for (A value : values) { accumulated = monoid.combine(accumulated, mapper.apply(value)); } return accumulated; }
public static <T> T combineAll(Iterable<T> values, Monoid<T> monoid) { return foldMap(values, monoid, Fn1.identity()); }}foldMap is the workhorse: map each element into a monoidal type, then combine. “Total income across applications” is foldMap(apps, Monoid.longSum(), ApplicationInput::declaredIncome). foldRight (in the repo) walks from the back — the distinction matters for laziness in Part 14.
Traverse and sequence: turning a list of contexts inside out
The scheme engine collects document-verification results: a List<Result<Document, RegistryError>>. What the caller wants is Result<List<Document>, RegistryError> — all the documents, or the first failure:
public static <T, E> Result<List<T>, E> sequence(List<Result<T, E>> results) { List<T> values = new ArrayList<>(results.size()); for (Result<T, E> result : results) { switch (result) { case Result.Success<T, E>(var value) -> values.add(value); case Result.Failure<T, E>(var error) -> { return Result.failure(error); } } } return Result.success(values);}traverse is sequence composed with a per-element function — apply verify(document) to each element, collecting Results as you go. And when “first failure” is wrong — when the citizen should see every invalid document — Validated.sequence accumulates instead:
public static <E, T> Validated<E, List<T>> sequence( List<Validated<E, T>> values, Semigroup<E> semigroup) { Validated<E, List<T>> accumulated = valid(new ArrayList<>()); for (Validated<E, T> value : values) { accumulated = map2(accumulated, value, semigroup, (list, element) -> { list.add(element); return list; }); } return accumulated.map(List::copyOf);}The choice is the Part 8 rule applied to collections: dependent → Result.sequence stops early; independent → Validated.sequence reports everything. Pick per workflow, not per type.
Compared with the JDK: Stream.collect with a Collector is a fold; Collectors.reducing is foldMap for a monoid you write inline. The jfp versions exist to make the structure visible — foldMap names the monoid explicitly, which is the difference between “an accumulator that happens to work” and “an accumulation you can state laws about.”