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?

What You'll Learn

  • 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 TypeWrapsError TypeEvaluationKey Use Case
MaybePath<A>Maybe<A>None (absence)ImmediateOptional values
EitherPath<E, A>Either<E, A>E (typed)ImmediateTyped error handling
TryPath<A>Try<A>ThrowableImmediateException wrapping
IOPath<A>IO<A>ThrowableDeferredSide effects
ValidationPath<E, A>Validated<E, A>E (accumulated)ImmediateForm validation
IdPath<A>Id<A>None (always succeeds)ImmediatePure values
OptionalPath<A>Optional<A>None (absence)ImmediateJava stdlib bridge
GenericPath<F, A>Kind<F, A>Depends on monadDependsCustom monads
TrampolinePath<A>Trampoline<A>NoneDeferredStack-safe recursion
FreePath<F, A>Free<F, A>NoneInterpretedDSL building
FreeApPath<F, A>FreeAp<F, A>NoneInterpretedApplicative DSLs

Path Types by Category

Value Containers (Immediate Evaluation)

These types wrap values and evaluate operations immediately:

Error Handling (Immediate Evaluation)

These types handle failures with different strategies:

Deferred Computation

These types describe computations without executing them:

DSL Building

These types support building domain-specific languages:

Universal


Summary: Choosing Your Vessel

ScenarioPath TypeWhy
Value might be absentMaybePathSimple presence/absence
Operation might fail with typed errorEitherPathStructured error handling
Wrapping exception-throwing codeTryPathException → functional bridge
Side effects to deferIOPathLazy, referential transparency
Need ALL validation errorsValidationPathError accumulation
Bridging Java's OptionalOptionalPathStdlib compatibility
Always succeeds, pure valueIdPathGeneric/testing contexts
Custom monadGenericPathUniversal escape hatch
Deep recursion without stack overflowTrampolinePathStack-safe trampolining
DSL with sequential operationsFreePathInterpretable programs
DSL with independent operationsFreeApPathStatic 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.

See Also


Previous: Capability Interfaces Next: MaybePath