Decision Trees
Three trees, one page
- Which optic type to choose for a given data shape and access pattern.
- Which API style (Focus DSL, manual composition, Fluent API, Free Monad DSL) to choose for a given task.
- Which advanced feature (filtered, indexed, profunctor) solves which specific problem.
The decision trees that appear in scattered form across the chapter intros are consolidated here. Use this page when you need to route quickly to the right tool.
Tree 1: Which optic do I need?
┌─────────────────────┐
│ What are you doing? │
└──────────┬──────────┘
│
┌────────────────────┼────────────────────┐
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Reading │ │ Modifying │ │ Transforming│
│ only? │ │ values? │ │ types? │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
▼ │ ▼
┌─────────────┐ │ ┌─────────────┐
│ How many │ │ │ ISO │
│ targets? │ │ └─────────────┘
└──────┬──────┘ │
│ │
┌──────┴──────┐ │
▼ ▼ ▼
┌───────┐ ┌──────────┐ ┌─────────────┐
│ One │ │Zero-more │ │ How many │
│ │ │ │ │ targets? │
└───┬───┘ └────┬─────┘ └──────┬──────┘
│ │ │
▼ ▼ ┌──────┴──────┐
┌───────┐ ┌────────┐ ▼ ▼
│GETTER │ │ FOLD │ ┌───────┐ ┌──────────┐
└───────┘ └────────┘ │ One │ │Zero-more │
└───┬───┘ └────┬─────┘
│ │
┌─────────┴───┐ │
▼ ▼ ▼
┌──────────┐ ┌─────────┐ ┌──────────┐
│ Required │ │Optional │ │TRAVERSAL │
└────┬─────┘ └────┬────┘ └──────────┘
│ │
▼ ▼
┌────────┐ ┌─────────┐
│ LENS │ │ PRISM │
└────────┘ └─────────┘
| You have... | You want to... | Reach for |
|---|---|---|
| A required field on a record | Get and set | Lens |
| A variant of a sealed type | Match and modify the variant | Prism |
An optional field (nullable, Optional-wrapped) | Get and set if present | Affine |
| Two equivalent representations | Convert losslessly | Iso |
| A collection field | Apply an operation to every element | Traversal |
| A collection field, read-only | Query, search, aggregate | Fold |
| Read-only access to a single field | Get only | Getter |
| Write-only access to a single field | Set only | Setter |
Tree 2: Which API style?
┌─────────────────────────────────────────────────────────────────────────────┐
│ CHOOSING YOUR API │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ │
│ │ Focus DSL │ ◄─── START HERE │
│ │ (Recommended) │ Path-based navigation with full type safety │
│ └────────┬────────┘ CompanyFocus.departments().employees().name() │
│ │ │
│ │ Need validation-aware modifications? │
│ │ Working with Either/Maybe/Validated? │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Fluent API │ Static methods + builders for effectful ops │
│ │ (OpticOps) │ OpticOps.modifyEither(user, lens, validator) │
│ └─────────────────┘ │
│ │
│ Need audit trails, dry-runs, or multiple execution strategies? │
│ See Advanced Optics for the Free Monad DSL. │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
| Your task | Use |
|---|---|
| Update a nested record field | Focus DSL |
| Compose optics across types you own | Focus DSL |
Validate as you modify (Either, Validated, Maybe) | Fluent API |
| Fan out an effect across a collection | Fluent API modifyAllF |
| Build optic operations as data, run later | Free Monad DSL |
| Audit trail of every optic operation | Free Monad DSL with logging interpreter |
| Reuse an optic for a type you cannot annotate | @ImportOptics or an OpticsSpec interface |
| Adapt an optic to a different data shape | Profunctor contramap / map / dimap |
Tree 3: Which advanced feature?
┌─────────────────────────────┐
│ What is the constraint? │
└──────────────┬──────────────┘
│
┌────────────────────────┼────────────────────────┐
▼ ▼ ▼
┌──────────┐ ┌────────────┐ ┌──────────────┐
│ Subset │ │ Position │ │ Type adapter │
│ matters │ │ matters │ │ for source/ │
│ │ │ │ │ target │
└────┬─────┘ └─────┬──────┘ └──────┬───────┘
│ │ │
▼ ▼ ▼
┌──────────┐ ┌────────────┐ ┌──────────────┐
│ Filtered │ │ Indexed │ │ Profunctor │
│ optics │ │ optics │ │ optics │
└──────────┘ └────────────┘ └──────────────┘
| Your problem | Reach for |
|---|---|
| "Apply only to elements matching a predicate" | Filtered Optics |
| "I need the index alongside each element" | Indexed Optics |
"Access by key in a Map" | Indexed Access and the At typeclass |
| "Apply over every element of a custom container" | Each Typeclass |
"Operate on individual characters of a String" | String Traversals |
| "Adapt a lens for a different source record type" | lens.contramap(...) (Profunctor Optics) |
| "Adapt a lens for a different target type" | lens.map(...) (Profunctor Optics) |
| "Both source and target need adapting" | lens.dimap(...) (Profunctor Optics) |
| "Match a sum-type variant by a predicate, not type" | Advanced Prism Patterns: nearly and doesNotMatch |
See also
- Optic Capabilities, what each optic can do once you have chosen one.
- Composition Rules, what type results from composing two optics.
- Annotations at a Glance, which annotation generates each optic.
Previous: Production Readiness