A fluent for-comprehension API for Java, inspired by Scala's for-expressions. It allows the composition of multiple monadic or collection-like types in a type-safe and readable manner.
Maven:
<dependency>
<groupId>com.pivovarit</groupId>
<artifactId>for-comprehension4j</artifactId>
<version>0.0.1</version>
</dependency>Gradle (Kotlin DSL):
implementation("com.pivovarit:for-comprehension4j:0.0.1")Gradle (Groovy DSL):
implementation 'com.pivovarit:for-comprehension4j:0.0.1'All examples assume:
import static com.pivovarit.forc.ForComprehension.forc;Instead of nesting multiple flatMap and map calls, you can use forc to simulate a for-comprehension.
Eager (all inputs are provided upfront):
Optional<Integer> width = Optional.of(3);
Optional<Integer> height = Optional.of(4);
Optional<Integer> area = forc(width, height)
.yield((w, h) -> w * h);
// area -> Optional[12]Lazy (later values can depend on earlier ones):
Optional<Integer> width = Optional.of(3);
Optional<Integer> area = forc(
width,
w -> Optional.of(w + 1)
).yield((w, h) -> w * h);
// area -> Optional[12]All comprehension types support an optional .filter() step that acts as a guard, keeping only
combinations where the predicate holds. Multiple .filter() calls are AND-composed.
Optional - guard failure produces Optional.empty():
Optional<Integer> width = Optional.of(4);
Optional<Integer> height = Optional.of(3);
Optional<Integer> area = forc(width, height)
.filter((w, h) -> w > h)
.yield((w, h) -> w * h);
// area -> Optional[12] (4 > 3, so the guard passes)Optional<Integer> area = forc(Optional.of(2), Optional.of(3))
.filter((w, h) -> w > h)
.yield((w, h) -> w * h);
// area -> Optional.empty() (2 > 3 is false, guard fails)Stream / Iterable - guard filters the cartesian product:
Stream<String> roles = Stream.of("admin", "user");
Stream<String> scopes = Stream.of("read", "write");
Stream<String> allowed = forc(roles, scopes)
.filter((role, scope) -> !(role.equals("user") && scope.equals("write")))
.yield((role, scope) -> role + ":" + scope);
// allowed -> ["admin:read", "admin:write", "user:read"].filter() works the same way on lazy comprehensions:
// Optional - guard is applied after all values are bound
Optional<Integer> area = forc(
Optional.of(4),
w -> Optional.of(w - 2) // height depends on width
).filter((w, h) -> w > h)
.yield((w, h) -> w * h);
// area -> Optional[8] (4 > 2, guard passes)// Stream - filter applied to the lazily-generated cartesian product
Stream<String> result = forc(
Stream.of("admin", "user"),
role -> Stream.of(role + ":read", role + ":write")
).filter((role, permission) -> !permission.equals("user:write"))
.yield((role, permission) -> permission);
// result -> ["admin:read", "admin:write", "user:read"]// Iterable
List<String> result = forc(
List.of("admin", "user"),
role -> List.of(role + ":read", role + ":write")
).filter((role, permission) -> !permission.equals("user:write"))
.yield((role, permission) -> permission);
// result -> ["admin:read", "admin:write", "user:read"].filter() is available for all arities and container types.
The same approach works with Stream values, producing a cartesian product of all elements.
Eager:
Stream<Integer> s1 = Stream.of(1, 2);
Stream<Integer> s2 = Stream.of(10, 20);
Stream<Integer> result = forc(s1, s2)
.yield(Integer::sum);
// result -> [11, 21, 12, 22]Lazy (the second stream can depend on elements from the first):
Stream<Integer> result = forc(
Stream.of(1, 2),
v1 -> Stream.of(v1 * 10, v1 * 20)
).yield(Integer::sum);
// result -> [11, 21, 22, 42]Note on
Stream: streams are single-use. In the eagerStreamvariant, later streams may be materialized internally to support generating the cartesian product reliably. If you need fully lazy behavior end-to-end, use the lazy overload.
Iterable works similarly, producing a cartesian product and returning a List<R>.
Eager:
Iterable<Integer> i1 = List.of(1, 2);
Iterable<Integer> i2 = List.of(10, 20);
List<Integer> result = forc(i1, i2)
.yield(Integer::sum);
// result -> [11, 21, 12, 22]Lazy:
List<Integer> result = forc(
List.of(1, 2),
v1 -> List.of(v1 * 10, v1 * 20)
).yield(Integer::sum);
// result -> [11, 21, 22, 42]