Add type-safe handler system with phantom-type layers#5
Open
Add type-safe handler system with phantom-type layers#5
Conversation
Introduces four new types in the capability package that let the compiler verify at build time that every capability an effect requires has a handler: * Empty — phantom type representing an empty (no-capability) environment * With<A,B> — phantom type encoding the union of two capability requirements * HandlerEnv<R> — typed wrapper around CapabilityHandler<?> whose phantom parameter R tracks which capabilities are provided; compose with .and() * Layer<RIn,E,ROut> — recipe for building a HandlerEnv<ROut> from a HandlerEnv<RIn>, possibly performing effects during construction; supports horizontal (.and) and vertical (.andProvide) composition * TypedEffect<R,E,A> — thin wrapper around Effect<E,A> that declares R as the required capability environment; .run(env, runtime) only compiles when the caller holds a HandlerEnv<R> that matches TypeSafeHandlerTest covers: single-cap envs, and() composition, associativity, TypedEffect combinators (map/flatMap), pure effects, Layer.succeed, horizontal layer composition (++), vertical layer composition (>>> / andProvide where a layer reads config during construction), end-to-end layer→env→typedEffect integration, and interop escape hatches (fromHandler / HandlerEnv.fromHandler). https://claude.ai/code/session_01TBuJE3hwUJpjr2avujhXbL
…S doc All factory methods that previously accepted ThrowingFunction<C, ?> now use an F-bound to capture the capability's declared result type R: <C extends Capability<?>> → <R, C extends Capability<R>> ThrowingFunction<C, ?> → ThrowingFunction<C, R> Affected methods: CapabilityHandler.Builder.on() HandlerEnv.of() Layer.succeed() Layer.fromEffect() The compiler now verifies that handler lambdas return the exact type R that the capability family declares, with no wildcards at call sites. Also adds docs/TYPED_LAYERS.md, a comprehensive guide to the typed layer system (HandlerEnv, Layer, TypedEffect, Empty, With) with worked examples, a ZIO comparison table, and notes on Java-specific trade-offs. docs/CAPABILITIES.md updated with a pointer to the new guide. https://claude.ai/code/session_01TBuJE3hwUJpjr2avujhXbL
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Introduces a ZIO-style layer system for roux that uses phantom types to enforce compile-time verification that every capability an effect requires has a corresponding handler. This eliminates the possibility of runtime
UnsupportedOperationExceptiondue to missing handlers — the program simply won't compile if capabilities are unhandled.Key Changes
HandlerEnv<R>— A typed wrapper aroundCapabilityHandlerwhere the phantom type parameterRtracks which capabilities are provided. Supports composition viaand()to merge multiple capability environments.TypedEffect<R, E, A>— A thin wrapper aroundEffect<E, A>that statically declares capability requirements via phantom typeR. Therun()method only compiles when the caller provides aHandlerEnv<R>with matching phantom type.Layer<RIn, E, ROut>— A recipe for building aHandlerEnv<ROut>from aHandlerEnv<RIn>, with support for both horizontal composition (and()— merge outputs) and vertical composition (andProvide()— feed one layer's output into another's input).Phantom type system —
EmptyandWith<A, B>form a type-level set of capabilities:Emptyrepresents no capabilities (base case)With<A, B>represents bothAandBcapabilitiesWith<A, With<B, C>>F-bounded polymorphism — All factory methods (
HandlerEnv.of,Layer.succeed,Layer.fromEffect) use F-bounds (<R, C extends Capability<R>>) to eliminate wildcards and ensure handler return types match capability declarations at compile time.Comprehensive test suite —
TypeSafeHandlerTestdemonstrates single and combined environments, typed effect execution, layer composition (horizontal and vertical), and full end-to-end integration.Documentation — New
TYPED_LAYERS.mdguide covering motivation, phantom types, all three abstractions, composition patterns, and design notes on how Java's approach differs from ZIO.Implementation Details
HandlerEnvis a functional interface backed by the existingCapabilityHandler— zero runtime overheadTypedEffectis a single-field wrapper aroundEffect— erased at runtime.classfile signatures; no instances are ever createdand,andProvide) thread the phantom types correctly through the type systemCapabilityHandlerAPI remains unchanged; new types layer on top for opt-in type safetyhttps://claude.ai/code/session_01TBuJE3hwUJpjr2avujhXbL