Path Types
"It is not down on any map; true places never are."
— Herman Melville, Moby-Dick
Melville was speaking of Queequeg's island home, but the observation applies to software: the territory you're navigating (nullable returns, network failures, validation errors, deferred effects) isn't marked on any class diagram. You need to choose your vessel before setting sail.
This chapter covers each Path type in detail. But before cataloguing the fleet, a more pressing question: which one do you need?
- How to choose the right Path type for your situation
- Overview of all available Path types
- When each type is the right tool, and when it isn't
Choosing Your Path
Before diving into specifics, orient yourself by the problem you're solving:
"The value might not exist"
You're dealing with absence: a lookup that returns nothing, an optional configuration, a field that might be null.
Reach for MaybePath if absence is normal and expected, not an error
condition. Nobody needs to know why the value is missing.
Reach for OptionalPath if you're bridging to Java's Optional ecosystem
and want to stay close to the standard library.
"The operation might fail, and I need to know why"
Something can go wrong, and the error carries information: a validation message, a typed error code, a domain-specific failure.
Reach for EitherPath when you control the error type and want typed,
structured errors.
Reach for TryPath when you're wrapping code that throws exceptions and
want to stay in exception-land (with Throwable as the error type).
"I need ALL the errors, not just the first"
Multiple independent validations, and stopping at the first failure would be unkind to your users.
Reach for ValidationPath with zipWithAccum to accumulate every error.
"The operation has side effects I want to defer"
You're reading files, calling APIs, writing to databases, effects that shouldn't happen until you're ready.
Reach for IOPath to describe the effect without executing it. Nothing
runs until you call unsafeRun().
"I need stack-safe recursion"
Deep recursive algorithms that would blow the stack with direct recursion.
Reach for TrampolinePath for guaranteed stack safety regardless of depth.
"I want to build an interpretable DSL"
Separate description from execution, test with mock interpreters, or support multiple interpretation strategies.
Reach for FreePath for sequential DSLs or
FreeApPath for parallel/static-analysis-friendly DSLs.
"The operation always succeeds"
No failure case, no absence; you just want Path operations on a pure value.
Reach for IdPath when you need a trivial Path for generic code or testing.
"None of the above"
You have a custom monad, or you're writing highly generic code.
Reach for GenericPath as the escape hatch; it wraps any Kind<F, A>
with a Monad instance.
Quick Reference
| Path Type | Wraps | Error Type | Evaluation | Key Use Case |
|---|---|---|---|---|
MaybePath<A> | Maybe<A> | None (absence) | Immediate | Optional values |
EitherPath<E, A> | Either<E, A> | E (typed) | Immediate | Typed error handling |
TryPath<A> | Try<A> | Throwable | Immediate | Exception wrapping |
IOPath<A> | IO<A> | Throwable | Deferred | Side effects |
ValidationPath<E, A> | Validated<E, A> | E (accumulated) | Immediate | Form validation |
IdPath<A> | Id<A> | None (always succeeds) | Immediate | Pure values |
OptionalPath<A> | Optional<A> | None (absence) | Immediate | Java stdlib bridge |
GenericPath<F, A> | Kind<F, A> | Depends on monad | Depends | Custom monads |
TrampolinePath<A> | Trampoline<A> | None | Deferred | Stack-safe recursion |
FreePath<F, A> | Free<F, A> | None | Interpreted | DSL building |
FreeApPath<F, A> | FreeAp<F, A> | None | Interpreted | Applicative DSLs |
Path Types by Category
Value Containers (Immediate Evaluation)
These types wrap values and evaluate operations immediately:
- MaybePath - For values that might be absent
- OptionalPath - Bridge to Java's
Optional - IdPath - Always contains a value (identity)
Error Handling (Immediate Evaluation)
These types handle failures with different strategies:
- EitherPath - Typed errors, short-circuit on first failure
- TryPath - Exception-based errors
- ValidationPath - Accumulate all errors
Deferred Computation
These types describe computations without executing them:
- IOPath - Side effects, runs when you call
unsafeRun() - TrampolinePath - Stack-safe recursion
DSL Building
These types support building domain-specific languages:
- FreePath - Sequential, monadic DSLs
- FreeApPath - Parallel, applicative DSLs
Universal
- GenericPath - Works with any monad
Summary: Choosing Your Vessel
| Scenario | Path Type | Why |
|---|---|---|
| Value might be absent | MaybePath | Simple presence/absence |
| Operation might fail with typed error | EitherPath | Structured error handling |
| Wrapping exception-throwing code | TryPath | Exception → functional bridge |
| Side effects to defer | IOPath | Lazy, referential transparency |
| Need ALL validation errors | ValidationPath | Error accumulation |
| Bridging Java's Optional | OptionalPath | Stdlib compatibility |
| Always succeeds, pure value | IdPath | Generic/testing contexts |
| Custom monad | GenericPath | Universal escape hatch |
| Deep recursion without stack overflow | TrampolinePath | Stack-safe trampolining |
| DSL with sequential operations | FreePath | Interpretable programs |
| DSL with independent operations | FreeApPath | Static analysis, parallel |
The choice isn't always obvious, and that's fine. You can convert between
types as your needs evolve (MaybePath to EitherPath when you need error
messages, TryPath to EitherPath when you want typed errors). The
Type Conversions chapter covers these conversions in detail.
- Composition Patterns - Combining and sequencing Path operations
- Type Conversions - Converting between Path types
- Patterns and Recipes - Common usage patterns
Previous: Capability Interfaces Next: MaybePath