Monad Transformers Journey
Estimated Duration: ~90 minutes (four sub-journeys) | Exercises: ~28
The Effect Path API (Tutorial 01: Effect Path Basics) covers most workflows you will write in Java. Take this journey when you have hit one of the corners that Path types do not reach: integrating with code that returns a different outer monad, or writing library code that should work against any caller's effect stack.
What You'll Learn
The Monad Transformers Journey builds on the Effect Path API to cover the cases where you need to drop down to the underlying transformer machinery.
Tutorial 01: When Path Isn't Enough (~30 min, 6 exercises)
- Bridging an existing
CompletableFuture<Either<L, R>>intoEitherT - Lifting synchronous
Eithervalues into the same workflow - Composing async-and-typed-error steps with
Forcomprehensions - Recovering from typed errors with
handleErrorWith - Collapsing back to ordinary Java at the boundary
Tutorial 02: Async with Absence (~25 min, 5 exercises)
- The same shape applied to
CompletableFuture<Optional<T>> - Chaining async lookups with
For - Providing defaults when a lookup yields nothing
- A short tour of
MaybeTfor codebases that preferMaybe
Tutorial 03: Stacking Transformers (~15 min, 4 exercises)
- Two effects in one workflow:
EitherToverOptional - Why
Forkeeps stacked code readable - When stacking gets uncomfortable, what to reach for instead
Tutorial 04: Polymorphic Capabilities (MTL) (~30-40 min, 14 exercises)
MonadReader: read-only access to a shared environmentMonadState: read-write state threadingMonadWriter: append-only output accumulation- Writing polymorphic functions that accept capability interfaces, not concrete types
Why Transformers Second?
The Effect Path API is designed to be the primary interface. Reach for raw transformers only when:
- You need to integrate with code that already returns a different outer monad
- You are writing a library and want callers to plug in their own effect stack
- You need an outer monad that no Path type wraps
If none of those apply, stay on the Path API. Your code will be shorter and clearer.
Getting Started
The tutorials are located in hkj-examples/src/test/java/org/higherkindedj/tutorial/transformers/:
Tutorial 01: When Path Isn't Enough
File: Tutorial01_WhenPathIsNotEnough.java
// Bridge an existing Future<Either> into the transformer
var london = fetchWeather("London");
EitherT<CompletableFutureKind.Witness, WeatherError, WeatherReport> wrapped =
EitherT.fromKind(london);
// Compose two steps with For
var workflow = For.from(eitherTMonad, EitherT.fromKind(fetchWeather("Berlin")))
.yield(report -> new TravelAdvice(report.city(),
report.temperature() < 10 ? "Pack a coat" : "Travel light"));
Tutorial 02: Async with Absence
File: Tutorial02_AsyncWithAbsence.java
// Chain two async lookups
var workflow = For.from(optionalTMonad, OptionalT.fromKind(fetchUser("alice")))
.from(user -> OptionalT.fromKind(fetchProfile(user.id())))
.yield((user, profile) -> profile);
Tutorial 03: Stacking Transformers
File: Tutorial03_StackingTransformers.java
// EitherT layered over Optional: a value that may be absent AND may have failed validation
var sum = For.from(eitherTOverOptional,
EitherT.fromEither(optionalMonad, Either.<AppError, Integer>right(10)))
.from(_ -> EitherT.fromEither(optionalMonad, Either.<AppError, Integer>right(32)))
.yield((a, b) -> a + b);
Tutorial 04: Polymorphic Capabilities (MTL)
File: Tutorial04_PolymorphicCapabilities.java
// A function that declares "I need to read an AppConfig" and nothing else
static <F extends WitnessArity<TypeArity.Unary>> Kind<F, String> buildUrl(
MonadReader<F, AppConfig> env) {
return For.from(env, env.ask())
.yield(c -> c.dbUrl() + "?retries=" + c.maxRetries());
}
Prerequisites
Before starting this journey, you should:
- Be comfortable with Java lambdas, generics, and
var - Have completed the Effect API Journey
Helpful background: skim the Monad Transformers Quickstart and Stack Archetypes so you have the high-level picture before drilling into exercises.
Running the Tutorials
# Run all transformer tutorials
./gradlew :hkj-examples:test --tests "*tutorial.transformers.*"
# Run a specific tutorial
./gradlew :hkj-examples:test --tests "*Tutorial01_WhenPathIsNotEnough*"
./gradlew :hkj-examples:test --tests "*Tutorial02_AsyncWithAbsence*"
./gradlew :hkj-examples:test --tests "*Tutorial03_StackingTransformers*"
./gradlew :hkj-examples:test --tests "*Tutorial04_PolymorphicCapabilities*"
Further Resources
After completing this journey, explore:
- Monad Transformers Quickstart, three runnable examples in 150 lines
- Stack Archetypes, seven named patterns
- Migration Cookbook, imperative and Path translations
- MTL Capabilities, the conceptual reference for Tutorial 04
- EitherT, OptionalT, MaybeT, ReaderT, StateT, WriterT, the per-transformer reference pages
What's Next?
After completing the transformers journey:
- Polymorphic library code: study MTL Combining Capabilities for multi-capability functions
- Production patterns: explore the order-workflow examples in
hkj-examples/src/main/java/org/higherkindedj/example/order/
Previous: Effect API Next: Concurrency: VTask