Skip to content

Typed effects with env and v0.3.0 release#6

Merged
contrasam merged 69 commits intomainfrom
feat/typed-effects-with-env
Apr 17, 2026
Merged

Typed effects with env and v0.3.0 release#6
contrasam merged 69 commits intomainfrom
feat/typed-effects-with-env

Conversation

@contrasam
Copy link
Copy Markdown
Contributor

No description provided.

contrasam and others added 30 commits March 8, 2026 10:57
Adds PROJECT.md for two-part initiative: verified law tests (functor/monad/error/capability) and test utilities (assertions, TestRuntime, capability test doubles).

Creates PROJECT.md with requirements and constraints.
6-phase milestone: law infrastructure, functor/monad laws, error/capability laws,
fluent assertions, TestRuntime, and capability test doubles.

Initializes STATE.md and phase directories.
Single plan: EffectLawSupport utility with observational effect equivalence
assertions, plus LawInfrastructureTest smoke tests.
One plan: FunctorLawsTest (identity, composition) and MonadLawsTest
(left identity, right identity, associativity), each tested with
success, failure, and suspend effect inputs.
One plan: ErrorChannelLawsTest (catchAll identity, mapError identity,
mapError composition, attempt round-trip) and CapabilityHandlerLawsTest
(orElse identity, compose associativity). 14 tests total.
One plan: EffectAssert fluent chain + EffectAssertions entry point with
eager execution, 8 assertion methods (succeeds/fails/with/predicate/andReturn/andError),
handler overload, and 22-test validation suite.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
contrasam and others added 7 commits March 10, 2026 20:23
Phase 11: finalize on() intersection bound (C extends F & Capability<R>),
commit user's forType() changes, and write end-to-end LayerIntegrationTest.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Note: Java JLS §4.4 forbids additional bounds when the first bound is a
type variable (F), so the exact intersection `C extends F & Capability<R>`
does not compile. The existing `C extends F` already transitively enforces
the Capability bound since Builder<F extends Capability<?>>. The forType()
factory and Builder<F> generics from the user's changes are retained as-is.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Documents the full Milestone 2 API: phantom types (Empty/With),
HandlerEnv, EffectWithEnv, Layer (succeed/fromEffect), horizontal
and vertical layer composition, API reference tables, and design notes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- CapabilityHandler.builder() marked @deprecated(since="0.3.0") — points
  to forType() and HandlerEnv.of()+and() for multi-family environments
- HandlerEnv.of() updated to use forType() internally (avoids deprecation
  warning in production code)
- CAPABILITIES.md: consolidate sections 6+7 into a single forType() section
- TYPED_EFFECTS.md: replace remaining builder() reference with forType()

All existing call sites continue to compile (forRemoval=false).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Milestone 1 (Phases 1-6): Effect Laws & Test Utilities — 11 algebraic
  laws, EffectAssertions, TestRuntime, TestCapabilityHandler
- Milestone 2 (Phases 7-11): Type-Safe Layer System — HandlerEnv,
  EffectWithEnv, Layer (succeed/fromEffect/and/andProvide), forType() API
- ROADMAP.md collapsed to two-line summaries with archive links
- STATE.md updated with all decisions and notes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@contrasam
Copy link
Copy Markdown
Contributor Author

@greptileai can you review this PR for me?

2 similar comments
@contrasam
Copy link
Copy Markdown
Contributor Author

@greptileai can you review this PR for me?

@contrasam
Copy link
Copy Markdown
Contributor Author

@greptileai can you review this PR for me?

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 1, 2026

Greptile Summary

This PR introduces a phantom-type layer system (EffectWithEnv, HandlerEnv, Layer, Empty, With) that tracks capability requirements at compile time, and refactors Effect.sleep from a Suspend wrapper into a first-class Sleep node — enabling TestRuntime to override it with a virtual TestClock for deterministic time-based tests. The implementation is well-structured, comprehensively tested, and prior review concerns (singleton EMPTY_INSTANCE, Builder genericity, Layer.fromEffect cleanup) have all been addressed.

Confidence Score: 5/5

Safe to merge — all remaining findings are P2 style/documentation suggestions with no correctness impact.

Prior review concerns (singleton EMPTY_INSTANCE allocation, Builder generics, Layer.fromEffect intermediate variable) are all addressed. The Sleep refactor, phantom-type layer system, and TestRuntime virtual clock are well-tested with comprehensive law and integration tests. The only open issue is a stale Javadoc comment.

No files require special attention.

Important Files Changed

Filename Overview
lib/src/main/java/com/cajunsystems/roux/EffectWithEnv.java New phantom-typed wrapper around Effect that statically declares capability requirements via type parameter R; well-documented, correct flatMap/map/run implementation.
lib/src/main/java/com/cajunsystems/roux/capability/HandlerEnv.java New typed environment wrapping CapabilityHandler with phantom type R; singleton EMPTY_INSTANCE, of/and/fromHandler all correct — stale "Phase 8 will add" Javadoc comment remains.
lib/src/main/java/com/cajunsystems/roux/capability/Layer.java New ZLayer-inspired recipe type; succeed/fromEffect factories and and/andProvide horizontal/vertical composition operators are correct; @FunctionalInterface with one abstract method is valid.
lib/src/main/java/com/cajunsystems/roux/Effect.java Sleep added as a new sealed subtype; sleep() smart constructor now returns Sleep node instead of Suspend wrapper, enabling runtime interception.
lib/src/main/java/com/cajunsystems/roux/runtime/DefaultEffectRuntime.java Sleep case added to both direct-execution switch and trampoline evaluateOne; performSleep hook is protected and overridable for test clocks.
lib/src/main/java/com/cajunsystems/roux/capability/CapabilityHandler.java builder() deprecated in favour of forType(); Builder now correctly parameterised as Builder; on() type-tightened to C extends F for improved lambda inference.
lib/src/test/java/com/cajunsystems/roux/testing/TestRuntime.java Clean override of performSleep to advance virtual TestClock instead of blocking; uses virtual-thread executor appropriate for test scenarios.
lib/src/test/java/com/cajunsystems/roux/testing/TestClock.java Thread-safe virtual clock using AtomicLong; advance/currentTime/reset API is minimal and correct.

Class Diagram

%%{init: {'theme': 'neutral'}}%%
classDiagram
    class EffectWithEnv~R,E,A~ {
        -Effect~E,A~ effect
        +of(Effect) EffectWithEnv~R,E,A~$
        +pure(Effect) EffectWithEnv~Empty,E,A~$
        +map(Function) EffectWithEnv~R,E,B~
        +flatMap(Function) EffectWithEnv~R,E,B~
        +run(HandlerEnv~R~, EffectRuntime) A
        +effect() Effect~E,A~
    }
    class HandlerEnv~R~ {
        -CapabilityHandler handler
        +of(Class, ThrowingFunction) HandlerEnv~C~$
        +fromHandler(CapabilityHandler) HandlerEnv~R~$
        +empty() HandlerEnv~Empty~$
        +and(HandlerEnv~S~) HandlerEnv~With~R,S~~
        +toHandler() CapabilityHandler
    }
    class Layer~RIn,E,ROut~ {
        <<interface>>
        +build(HandlerEnv~RIn~) Effect~E,HandlerEnv~ROut~~
        +succeed(Class, ThrowingFunction) Layer~Empty,RuntimeException,C~$
        +fromEffect(Class, Function) Layer~RIn,E,C~$
        +and(Layer) Layer~RIn,Throwable,With~ROut,S~~
        +andProvide(Layer) Layer~RIn,Throwable,With~ROut,S~~
    }
    class TestRuntime {
        -TestClock clock
        +create() TestRuntime$
        #performSleep(Duration)
    }
    class TestClock {
        -AtomicLong virtualNanos
        +advance(Duration)
        +currentTime() Duration
        +reset()
    }
    EffectWithEnv --> HandlerEnv : run uses
    HandlerEnv --> Layer : built by
    TestRuntime --|> DefaultEffectRuntime
    TestRuntime --> TestClock
Loading

Reviews (3): Last reviewed commit: "fix: document intentional local var in L..." | Re-trigger Greptile

Comment thread RELEASE_NOTES_0.2.2.md
@@ -0,0 +1,13 @@
# Roux v0.2.2
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Release notes version mismatch

The PR title is "Typed effects with env and v0.3.0 release", but the only release-notes file added is RELEASE_NOTES_0.2.2.md, which describes a v0.2.2 patch. There is no RELEASE_NOTES_0.3.0.md anywhere in this diff.

If this PR is intended as the v0.3.0 release, the significant new features — EffectWithEnv, HandlerEnv, Layer, phantom types (Empty/With), virtual-time TestRuntime, and the Sleep effect node — should all be documented in a dedicated RELEASE_NOTES_0.3.0.md.

Comment thread lib/src/main/java/com/cajunsystems/roux/capability/HandlerEnv.java Outdated
Comment thread lib/src/main/java/com/cajunsystems/roux/capability/CapabilityHandler.java Outdated
Comment thread lib/src/main/java/com/cajunsystems/roux/capability/Layer.java
@contrasam
Copy link
Copy Markdown
Contributor Author

@greptileai i did not push some changes, pushed all the changes now

java.util.List<Effect<E, A>> effects
) implements Effect<Throwable, A> {}
/** Suspends execution for the given duration. Interpreted by the runtime. */
record Sleep<E extends Throwable>(Duration duration) implements Effect<E, Unit> {}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Sleep is a source-breaking sealed subtype addition

Sleep is added as a new permitted subtype of the public sealed interface Effect. Any consumer code that performs an exhaustive switch expression over all Effect variants will now fail to compile with a missing case error. For example:

// Previously compiled; now fails after upgrading
return switch (effect) {
    case Effect.Pure<E,A> p     -> ...
    case Effect.Fail<E,A> f     -> ...
    case Effect.Suspend<E,A> s  -> ...
    // ... (all prior cases) ...
    // missing: case Effect.Sleep<?> s -> ...  ← compile error
};

Since Effect is a public API and sealed interfaces enforce exhaustiveness at compile time, this is a source-breaking change. It should be called out explicitly in the v0.3.0 migration notes, noting that any custom EffectRuntime implementations must add a Sleep branch.

contrasam and others added 11 commits April 17, 2026 10:46
Phase 12: Greptile code quality fixes (HandlerEnv.empty static constant,
forType @SuppressWarnings, Thread.sleep(Duration) precision fix).
Phase 13: RELEASE_NOTES_0.3.0.md with breaking-change migration guide.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
HandlerEnv.empty() static constant, forType @SuppressWarnings,
Thread.sleep(Duration) precision fix.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…is truncation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Version bump (build.gradle + README + PUBLISHING), CHANGELOG [0.3.0] entry,
RELEASE_NOTES_0.3.0.md with features, breaking changes, migration checklist.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Highlights, breaking-change migration guide, known limitations,
installation snippets, and full changelog compare link.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@contrasam
Copy link
Copy Markdown
Contributor Author

@greptileai fixed your comments, give me a confidence score to merge

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 17, 2026

Confidence score: 72/100

Here's the breakdown of what's still open:

Comment Priority Status Notes
Release notes version mismatch (PR says v0.3.0, file is v0.2.2) P1 ❌ Unresolved No RELEASE_NOTES_0.3.0.md found
Sleep is a source-breaking sealed subtype — needs migration notes P1 ❌ Unresolved Critical for any existing switch consumers
forType — missing @SuppressWarnings + inline comment P2 ❌ Unresolved Minor, but still flagged
Redundant result variable in fromEffect P2 ❌ Unresolved Trivial cleanup
HandlerEnv.empty() unnecessary allocation P2 ✅ Outdated (fixed)
Builder generics breaking change P2 ✅ Resolved

The two P1s are the blockers. For a library release (v0.3.0), documenting breaking changes in Effect (sealed subtype addition) and having accurate release notes are important — any downstream consumers performing exhaustive switch on Effect will get a compile error after upgrading without warning. The P2s are polish items that won't block correctness but are easy wins.

Address the P1s and this is easily merge-ready.

Tip: You can customize Greptile's behavior for this repo with .greptile/rules and .greptile/config.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@contrasam
Copy link
Copy Markdown
Contributor Author

@greptileai can you check for confidence now?

@contrasam contrasam merged commit 135ee5b into main Apr 17, 2026
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.

1 participant