Conversation
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.
…, and associativity
One plan: ErrorChannelLawsTest (catchAll identity, mapError identity, mapError composition, attempt round-trip) and CapabilityHandlerLawsTest (orElse identity, compose associativity). 14 tests total.
…nd compose associativity
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.
…nterfaces not allowed)
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>
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>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…t factories 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>
Phase 10 plan: horizontal and vertical Layer composition operators. Covers widenError helper, and() (same input, merged output), andProvide() (sequential build, both outputs retained), and LayerCompositionTest. 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>
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>
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>
|
@greptileai can you review this PR for me? |
2 similar comments
|
@greptileai can you review this PR for me? |
|
@greptileai can you review this PR for me? |
Greptile SummaryThis PR introduces the typed-effects-with-environment system for v0.3.0: a phantom-type layer ( Key findings:
Confidence Score: 4/5Safe to merge after addressing the One P1 finding remains: adding lib/src/main/java/com/cajunsystems/roux/Effect.java (new sealed subtype), lib/src/main/java/com/cajunsystems/roux/capability/HandlerEnv.java (interface-walk depth), lib/src/main/java/com/cajunsystems/roux/runtime/DefaultEffectRuntime.java (sleep precision) Important Files Changed
Class Diagram%%{init: {'theme': 'neutral'}}%%
classDiagram
direction TB
class `Effect~E,A~` {
<<sealed interface>>
}
class `Sleep~E~` {
<<record>>
+Duration duration
}
class `EffectWithEnv~R,E,A~` {
-Effect~E,A~ effect
+of(Effect) EffectWithEnv
+pure(Effect) EffectWithEnv
+map(f) EffectWithEnv~R,E,B~
+flatMap(f) EffectWithEnv~R,E,B~
+run(HandlerEnv~R~, EffectRuntime) A
+effect() Effect~E,A~
}
class `HandlerEnv~R~` {
-CapabilityHandler~Capability?~ handler
+of(Class, ThrowingFunction) HandlerEnv~C~
+and(HandlerEnv~S~) HandlerEnv~With~R,S~~
+fromHandler(CapabilityHandler) HandlerEnv~R~
+empty() HandlerEnv~Empty~
+toHandler() CapabilityHandler
}
class `Layer~RIn,E,ROut~` {
<<functional interface>>
+build(HandlerEnv~RIn~) Effect~E,HandlerEnv~ROut~~
+succeed(Class, handler) Layer~Empty,RE,C~
+fromEffect(Class, effectFn) Layer~RIn,E,C~
+and(Layer) Layer~RIn,Throwable,With~ROut,S~~
+andProvide(Layer) Layer~RIn,Throwable,With~ROut,S~~
}
class Empty {
<<phantom interface>>
}
class `With~A,B~` {
<<phantom interface>>
}
class DefaultEffectRuntime {
+performSleep(Duration)
+unsafeRun(Effect) A
+unsafeRunWithHandler(Effect, handler) A
}
class TestRuntime {
-TestClock clock
+performSleep(Duration)
+clock() TestClock
}
class TestClock {
-AtomicLong virtualNanos
+advance(Duration)
+currentTime() Duration
+reset()
}
`Effect~E,A~` <|-- `Sleep~E~`
`EffectWithEnv~R,E,A~` --> `Effect~E,A~` : wraps
`EffectWithEnv~R,E,A~` --> `HandlerEnv~R~` : run requires
`HandlerEnv~R~` --> `Layer~RIn,E,ROut~` : produced by
`HandlerEnv~R~` ..> Empty : uses
`HandlerEnv~R~` ..> `With~A,B~` : uses
DefaultEffectRuntime <|-- TestRuntime
TestRuntime --> TestClock : delegates sleep to
Reviews (2): Last reviewed commit: "chore: archive Milestone 1 and Milestone..." | Re-trigger Greptile |
| @@ -0,0 +1,13 @@ | |||
| # Roux v0.2.2 | |||
There was a problem hiding this comment.
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.
| * Use with effects that have {@link Empty} requirements. | ||
| * | ||
| * <pre>{@code | ||
| * HandlerEnv<Empty> env = HandlerEnv.empty(); | ||
| * }</pre> | ||
| */ | ||
| public static HandlerEnv<Empty> empty() { | ||
| CapabilityHandler<Capability<?>> noOp = new CapabilityHandler<>() { | ||
| @Override | ||
| public <R> R handle(Capability<?> cap) { | ||
| throw new UnsupportedOperationException( | ||
| "No handler registered for capability: " + cap.getClass().getName()); |
There was a problem hiding this comment.
HandlerEnv.empty() allocates a new handler on every call
A new anonymous CapabilityHandler instance is created each time empty() is called. Since the no-op handler is stateless, it can be a private static constant that is reused, avoiding needless allocation.
| * Use with effects that have {@link Empty} requirements. | |
| * | |
| * <pre>{@code | |
| * HandlerEnv<Empty> env = HandlerEnv.empty(); | |
| * }</pre> | |
| */ | |
| public static HandlerEnv<Empty> empty() { | |
| CapabilityHandler<Capability<?>> noOp = new CapabilityHandler<>() { | |
| @Override | |
| public <R> R handle(Capability<?> cap) { | |
| throw new UnsupportedOperationException( | |
| "No handler registered for capability: " + cap.getClass().getName()); | |
| private static final HandlerEnv<Empty> EMPTY_INSTANCE = new HandlerEnv<>( | |
| new CapabilityHandler<>() { | |
| @Override | |
| public <R> R handle(Capability<?> cap) { | |
| throw new UnsupportedOperationException( | |
| "No handler registered for capability: " + cap.getClass().getName()); | |
| } | |
| }); | |
| public static HandlerEnv<Empty> empty() { | |
| return EMPTY_INSTANCE; | |
| } |
| * sealed interface AppCapability<R> extends Capability<R> { | ||
| * record Log(String msg) implements AppCapability<Unit> {} | ||
| * record GetValue(String key) implements AppCapability<String> {} | ||
| * } | ||
| * | ||
| * var handler = CapabilityHandler.forType(AppCapability.class) | ||
| * .on(AppCapability.Log.class, c -> { logger.info(c.msg()); return Unit.unit(); }) | ||
| * .on(AppCapability.GetValue.class, c -> "value-" + c.key()) |
There was a problem hiding this comment.
forType parameter is silently ignored at runtime
capabilityType is accepted but never read — it exists solely to drive compile-time type inference of F. While intentional, the compiler and IDEs will warn about the unused parameter. Adding @SuppressWarnings and a brief inline note would silence the warning and signal to future maintainers that the omission is deliberate:
| * sealed interface AppCapability<R> extends Capability<R> { | |
| * record Log(String msg) implements AppCapability<Unit> {} | |
| * record GetValue(String key) implements AppCapability<String> {} | |
| * } | |
| * | |
| * var handler = CapabilityHandler.forType(AppCapability.class) | |
| * .on(AppCapability.Log.class, c -> { logger.info(c.msg()); return Unit.unit(); }) | |
| * .on(AppCapability.GetValue.class, c -> "value-" + c.key()) | |
| @SuppressWarnings("unused") // capabilityType is used only for compile-time type inference of F | |
| static <F extends Capability<?>> Builder<F> forType(Class<F> capabilityType) { | |
| return new Builder<>(); | |
| } |
lib/src/main/java/com/cajunsystems/roux/capability/CapabilityHandler.java
Outdated
Show resolved
Hide resolved
| * Layer<Empty, Throwable, With<DbOps, AuditOps>> appLayer = dbLayer.and(auditLayer); | ||
| * HandlerEnv<With<DbOps, AuditOps>> env = | ||
| * runtime.unsafeRun(appLayer.build(HandlerEnv.empty())); | ||
| * }</pre> | ||
| * |
There was a problem hiding this comment.
Redundant intermediate variable in
fromEffect
result is assigned and immediately returned. It can be inlined:
| * Layer<Empty, Throwable, With<DbOps, AuditOps>> appLayer = dbLayer.and(auditLayer); | |
| * HandlerEnv<With<DbOps, AuditOps>> env = | |
| * runtime.unsafeRun(appLayer.build(HandlerEnv.empty())); | |
| * }</pre> | |
| * | |
| return env -> effectFn.apply(env).map(h -> HandlerEnv.of(type, h)); |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
|
@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> {} |
There was a problem hiding this comment.
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.
No description provided.