ForPath Traverse
A common pattern in effectful programming is having a collection of items where each item needs to be processed through an effect -- validating every field in a form, fetching details for every ID in a list, or parsing every line in a file. Without traverse, you'd break out of the comprehension, process the collection separately, and thread the result back in. The traverse(), sequence(), and flatTraverse() methods let you do this inline, keeping the entire pipeline in one fluent chain.
Traverse: Apply an Effect to Every Element
traverse() takes a collection from the current tuple, applies a Path-producing function to each element, and collects the results. If any element's effect fails, the entire computation short-circuits according to the Path type's semantics.
MaybePath with Traverse
Here every positive number is scaled by 10. If any element were non-positive, the whole result would be Nothing:
var listTraverse = ListTraverse.INSTANCE;
MaybePath<Kind<ListKind.Witness, Integer>> result =
ForPath.from(Path.just(LIST.widen(List.of(1, 2, 3))))
.traverse(listTraverse,
t -> t._1(),
n -> n > 0 ? Path.just(n * 10) : Path.<Integer>nothing())
.yield((original, transformed) -> transformed);
// Result: Just([10, 20, 30])
// If any element fails the check, the entire result is Nothing.
EitherPath with Traverse
The same all-or-nothing pattern works with EitherPath, where failure carries a descriptive error message:
EitherPath<String, Kind<ListKind.Witness, String>> result =
ForPath.from(Path.<String, List<String>>right(
LIST.widen(List.of("alice", "bob"))))
.traverse(listTraverse,
t -> t._1(),
name -> name.length() > 2
? Path.<String, String>right(name.toUpperCase())
: Path.<String, String>left("Name too short: " + name))
.yield((original, uppercased) -> uppercased);
// Result: Right(["ALICE", "BOB"])
Sequence: Flip the Nesting
Sometimes you already have a collection of effectful values (e.g., a List<MaybePath<Integer>>) and need to "turn it inside-out" into a single effect containing the collection (MaybePath<List<Integer>>). sequence() does exactly this -- it's equivalent to traverse with the identity function.
FlatTraverse: Traverse and Flatten
When the mapping function itself returns a nested collection (e.g., each element produces a list), flatTraverse() traverses and flattens in one step, avoiding an intermediate nested structure.
// sequence: flip Structure<Effect<A>> to Effect<Structure<A>>
MaybePath<Kind<ListKind.Witness, Integer>> sequenced =
ForPath.from(Path.just(LIST.widen(
List.of(MAYBE.just(1), MAYBE.just(2), MAYBE.just(3)))))
.sequence(listTraverse, t -> t._1())
.yield((original, collected) -> collected);
// flatTraverse: traverse then flatten
MaybePath<Kind<ListKind.Witness, Integer>> flat =
ForPath.from(Path.just(LIST.widen(List.of(1, 2, 3))))
.flatTraverse(listTraverse, ListMonad.INSTANCE,
t -> t._1(),
n -> Path.just(LIST.widen(List.of(n, n * 10))))
.yield((original, flattened) -> flattened);
// Result: Just([1, 10, 2, 20, 3, 30])
- For the underlying type-class-based traverse within
Forcomprehensions, see Traverse Within Comprehensions. - For a detailed introduction to
Traverse,Foldable, andsequenceA, see Foldable & Traverse.
Previous: ForPath Parallel Composition | Next: Type Conversions