Skip to content

Conversation

@pivovarit
Copy link
Member

@pivovarit pivovarit commented Nov 3, 2025

Introduces a generator for lazy for-comprehension variants that produce intermediate ForLazyN<M> classes for supported monadic types (e.g., Option, Try, Either, etc.).

This addition complements the existing eager for-comprehension generator by introducing a lazy variant.

  • Added generation of ForLazyN<M> classes (arity 2 to N) that:
    • Use one monadic source (ts1) followed by a chain of dependent functions (ts2, ts3, …).
    • Allow lazy composition of monadic operations through nested lambdas.
  • Added corresponding For(...) factory methods for convenient construction.
  • Implemented a recursively generated yield(...) method with proper indentation and block structure for readability.

The generator handles mixed monadic/function parameter patterns such as:

Option<T1> ts1;
Function1<T1, Option<T2>> ts2;
Function2<T1, T2, Option<T3>> ts3;

expands for-comprehension with lazily-evaluated variants, so that the existing is possible:

Option<Integer> result = API.For(
  calculate1(),
  r1 -> calculate2(r1),
  (r1, r2) -> calculate3(r1, r2)).yield((r1, r2, r3) -> r1 + r2 + r3);

The generator enforces that the first argument is always a monadic value, not a function. This is necessary because:

  • The first argument acts as the entry point of the comprehension; it provides the initial context for all later flatMap calls.
  • If ts1 were also a function (e.g., Function0<M<T1>>), Java’s generic type inference fails to resolve chained type variables properly due to the presence of methods with similar erasure.
  • Subsequent arguments can safely be functions (FunctionN<T1, ..., TN, M<T(N+1)>>), since their input types are already determined by previous monadic values.

related: #3038

@pivovarit pivovarit force-pushed the for-lazy branch 2 times, most recently from 6e859c9 to 3b9dd81 Compare November 3, 2025 11:15
@pivovarit pivovarit changed the title Lazy For() Comprehension Lazy For() Comprehension Generator Nov 3, 2025
@pivovarit pivovarit force-pushed the for-lazy branch 3 times, most recently from 1700254 to 3c418d8 Compare November 3, 2025 12:21
@pivovarit pivovarit marked this pull request as ready for review November 3, 2025 13:17
@pivovarit pivovarit force-pushed the for-lazy branch 2 times, most recently from bb36dba to b170a51 Compare November 3, 2025 17:17
@adamkopec
Copy link
Contributor

This is really great, I've been using something very similar in my project, but in a way that only passed the previous argument. yours looks better and more consistent though :)

mine:

public static <L, T1, T2, T3, T4, T5> For5Either<L, T1, T2, T3, T4, T5> For(
        Supplier<Either<L, ? extends T1>> ts1,
        Function<? super T1, Either<L, ? extends T2>> ts2,
        Function<? super T2, Either<L, ? extends T3>> ts3,
        Function<? super T3, Either<L, ? extends T4>> ts4,
        Function<? super T4, Either<L, ? extends T5>> ts5
    ) {
        Objects.requireNonNull(ts1, "ts1 is null");
        Objects.requireNonNull(ts2, "ts2 is null");
        Objects.requireNonNull(ts3, "ts3 is null");
        Objects.requireNonNull(ts4, "ts4 is null");
        Objects.requireNonNull(ts5, "ts5 is null");
        return new For5Either<>(ts1, ts2, ts3, ts4, ts5);
    }
    
    public record For5Either<L, T1, T2, T3, T4, T5>(
        Supplier<Either<L, ? extends T1>> ts1,
        Function<? super T1, Either<L, ? extends T2>> ts2,
        Function<? super T2, Either<L, ? extends T3>> ts3,
        Function<? super T3, Either<L, ? extends T4>> ts4,
        Function<? super T4, Either<L, ? extends T5>> ts5
    ) {

        public For5Either(Either<L, T1> ts1, Either<L, T2> ts2, Either<L, T3> ts3, Either<L, T4> ts4, Either<L, T5> ts5) {
            this(() -> ts1, (ignored) -> ts2, (ignored) -> ts3, (ignored) -> ts4, (ignored) -> ts5);
        }

        public <R> Either<L, R> yield(Function5<? super T1, ? super T2, ? super T3, ? super T4, ? super T5, ? extends R> f) {
            Objects.requireNonNull(f, "f is null");
            return
                ts1.get().flatMap(t1 ->
                    ts2.apply(t1).flatMap(t2 ->
                        ts3.apply(t2).flatMap(t3 ->
                            ts4.apply(t3).flatMap(t4 ->
                                ts5.apply(t4).map(t5 -> f.apply(t1, t2, t3, t4, t5))
                        ))));
        }

        public Either<L, T5> yield() {
            return ts1.get()
                .flatMap(ts2)
                .flatMap(ts3)
                .flatMap(ts4)
                .flatMap(ts5);
        }
    }

Copy link
Contributor

@adamkopec adamkopec left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great job, just think about the variance and we're good to go :)

@pivovarit pivovarit force-pushed the for-lazy branch 2 times, most recently from 35d5c1f to 9378ef5 Compare November 6, 2025 07:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants