Foundations: Core Types

"Everything was beautiful and nothing hurt."

– Kurt Vonnegut, Slaughterhouse-Five


Prefer Effect Paths for Everyday Use

While this chapter documents the underlying core types, most applications should use the Effect Path API for cleaner composition and unified error handling. Effect Paths like EitherPath, MaybePath, and TryPath wrap these types and provide:

  • A consistent API across all effect types (map, via, recover)
  • Seamless integration with the Focus DSL
  • Railway-oriented programming for flat, readable pipelines

Use this chapter as a reference for understanding the foundations that power the Effect Path API.


Within a monadic context, certain complexities simply vanish. Null checks disappear inside Maybe. Error propagation becomes implicit within Either. Asynchronous callbacks flatten into sequential steps with CompletableFuture. The mess remains (it must), but the monad contains it, and within that containment, everything is beautiful and nothing hurts.

This chapter surveys the types that Higher-Kinded-J supports: seventeen distinct monads, each representing a different computational context. Some wrap standard Java types (Optional, List, CompletableFuture). Others are library-defined (Maybe, Either, IO, Validated). Still others handle advanced concerns like state management (State, Reader, Writer) or stack-safe recursion (Trampoline, Free).

Each type has its purpose. Id does nothing, which makes it useful as a baseline. Maybe and Optional handle absence. Either and Try handle failure with information. IO defers side effects. Lazy memoises computations. Reader threads configuration. State manages mutable state purely. Writer accumulates logs. Validated gathers all errors rather than stopping at the first.

The art lies in choosing the right context for the problem at hand. Sometimes you need fail-fast semantics; sometimes you need error accumulation. Sometimes laziness helps; sometimes it hinders. This chapter provides the vocabulary. Experience provides the judgement.

Hands On Practice


The Landscape

    ┌─────────────────────────────────────────────────────────────┐
    │  ABSENCE                                                    │
    │    Maybe         Optional         (value or nothing)        │
    └─────────────────────────────────────────────────────────────┘

    ┌─────────────────────────────────────────────────────────────┐
    │  FAILURE                                                    │
    │    Either        Try         Validated   (error handling)   │
    └─────────────────────────────────────────────────────────────┘

    ┌─────────────────────────────────────────────────────────────┐
    │  EFFECTS                                                    │
    │    IO       CompletableFuture    (deferred/async execution) │
    └─────────────────────────────────────────────────────────────┘

    ┌─────────────────────────────────────────────────────────────┐
    │  COLLECTIONS                                                │
    │    List          Stream           (multiple values)         │
    └─────────────────────────────────────────────────────────────┘

    ┌─────────────────────────────────────────────────────────────┐
    │  ENVIRONMENT                                                │
    │    Reader       State       Writer   (contextual computation)│
    └─────────────────────────────────────────────────────────────┘

    ┌─────────────────────────────────────────────────────────────┐
    │  CONTROL                                                    │
    │    Lazy      Trampoline      Free    (evaluation strategy)  │
    └─────────────────────────────────────────────────────────────┘

Choosing the Right Type

A rough decision guide:

NeedTypeWhy
Value might be absentMaybe or OptionalExplicit absence handling
Operation might failEither<E, A>Typed error with information
Might throw exceptionsTry<A>Captures Throwable
Accumulate all errorsValidated<E, A>Fail-slow validation
Defer side effectsIO<A>Referential transparency
Run asynchronouslyCompletableFuture<A>Non-blocking execution
Multiple resultsList<A> or Stream<A>Non-determinism
Read configurationReader<R, A>Dependency injection
Track state changesState<S, A>Pure state management
Log operationsWriter<W, A>Accumulate output
Deep recursionTrampoline<A>Stack safety
Build DSLsFree<F, A>Separate description from execution

What You'll Learn

In This Chapter

  • Identity – The trivial monad that simply wraps a value unchanged. Useful as a baseline for testing and as a placeholder in generic code.
  • Maybe and Optional – Two approaches to representing absent values. Maybe is the library's own implementation; Optional wraps Java's java.util.Optional.
  • Either – Represents computations that can fail with typed error information. Unlike exceptions, the error type is explicit in the signature.
  • Try – Captures exceptions as values, converting thrown errors into a failed Try. Bridges imperative exception handling with functional composition.
  • Validated – Like Either, but accumulates all errors rather than stopping at the first. Essential for form validation and batch processing.
  • List and Stream – Model non-deterministic computation where operations can produce multiple results. Stream adds laziness for infinite or large sequences.
  • CompletableFuture – Wraps Java's CompletableFuture for asynchronous computation, enabling monadic composition of async operations.
  • IO – Defers side effects until explicitly run, maintaining referential transparency. Effects are described, not executed, until interpretation.
  • Lazy – Delays computation until the value is needed, then memoises the result. Useful for expensive computations that may never be required.
  • Reader – Threads a read-only environment through computations. A functional approach to dependency injection.
  • State – Threads mutable state through a computation while maintaining purity. Each step receives state and produces new state alongside its result.
  • Writer – Accumulates output (typically logs) alongside computation results. The output type must be a Monoid for combining.
  • Trampoline – Enables stack-safe recursion by converting recursive calls into a heap-allocated data structure.
  • Free – Represents programs as data structures that can be interpreted in multiple ways. The foundation for building embedded DSLs.
  • Const – Ignores its second type parameter, carrying only the first. Useful for accumulating values during traversals.

Chapter Contents

  1. Supported Types - Overview of all seventeen monadic types
  2. CompletableFuture - Asynchronous computation
  3. Either - Typed, informative failure
  4. Identity - The simplest monad, doing nothing
  5. IO - Deferred side effects
  6. Lazy - Memoised computation
  7. List - Multiple values (non-determinism)
  8. Maybe - Handling absence
  9. Optional - Java's Optional as a monad
  10. Reader - Environment access
  11. State - Pure state threading
  12. Stream - Lazy sequences
  13. Trampoline - Stack-safe recursion
  14. Free - Programs as data
  15. Free Applicative - Static analysis of programs
  16. Coyoneda - Free functor for any type
  17. Try - Exception capture
  18. Validated - Error accumulation
  19. Writer - Output accumulation
  20. Const - Phantom-typed constants

Hands-On Learning

Practice real-world monad patterns in Tutorial 07: Real World (6 exercises, ~12 minutes).


Next: Supported Types