Decision Trees

Three trees, one page

What You'll Learn

  • 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?

flowchart TD
    Q{"What are you doing<br/>to the focus?"}
    Q -->|"reading only"| R{"How many<br/>targets?"}
    Q -->|"reading and writing"| M{"How many<br/>targets?"}
    Q -->|"converting between<br/>equivalent types"| I(["Iso"])

    R -->|"exactly one"| G(["Getter"])
    R -->|"zero or more"| F(["Fold"])

    M -->|"exactly one"| L(["Lens"])
    M -->|"zero or one:<br/>the field may be absent"| A(["Affine"])
    M -->|"zero or one:<br/>the value may be another variant"| P(["Prism"])
    M -->|"zero or more"| T(["Traversal"])

    classDef decision fill:#e5c890,stroke:#df8e1d,color:#232634
    classDef tier fill:#a6d189,stroke:#40a02b,color:#232634
    class Q,R,M decision
    class I,G,F,L,A,P,T tier

Write-only access is the one case the tree does not reach: that is a Setter, and you arrive at it by knowing you never read.

You have...You want to...Reach for
A required field on a recordGet and setLens
A variant of a sealed typeMatch and modify the variantPrism
An optional field (nullable, Optional-wrapped)Get and set if presentAffine
Two equivalent representationsConvert losslesslyIso
A collection fieldApply an operation to every elementTraversal
A collection field, read-onlyQuery, search, aggregateFold
Read-only access to a single fieldGet onlyGetter
Write-only access, one or many targetsSet or modify, never readSetter

Tree 2: Which API style?

flowchart TD
    S(["Start: Focus DSL<br/>CompanyFocus.headquarters().city()"]) --> Q{"Does the update<br/>need more?"}
    Q -->|"no: plain nested update"| S2(["stay on the Focus DSL"])
    Q -->|"it can fail, or accumulate errors"| FA(["Fluent API: OpticOps<br/>modifyEither, modifyAllValidated"])
    Q -->|"it must be inspected,<br/>audited or run several ways"| FM(["Free Monad DSL<br/>see Advanced Optics"])

    classDef decision fill:#e5c890,stroke:#df8e1d,color:#232634
    classDef tier fill:#a6d189,stroke:#40a02b,color:#232634
    classDef wire fill:#8caaee,stroke:#1e66f5,color:#232634
    class Q decision
    class S2,FA,FM tier
    class S wire
Your taskUse
Update a nested record fieldFocus DSL
Compose optics across types you ownFocus DSL
Validate as you modify (Either, Validated, Maybe)Fluent API
Fan out an effect across a collectionFluent API modifyAllF
Build optic operations as data, run laterFree Monad DSL
Audit trail of every optic operationFree 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 shapeCompose when the source nests, an Iso when the shapes are equivalent, Lens.of when it is lopsided, dimap when it is one-way

Tree 3: Which advanced feature?

flowchart TD
    Q{"What is the<br/>constraint?"}
    Q -->|"only some elements<br/>should be touched"| F(["Filtered optics"])
    Q -->|"the position matters<br/>as well as the value"| I(["Indexed optics"])
    Q -->|"the source or target<br/>is the wrong shape"| P(["Profunctor optics"])

    classDef decision fill:#e5c890,stroke:#df8e1d,color:#232634
    classDef tier fill:#a6d189,stroke:#40a02b,color:#232634
    class Q decision
    class F,I,P tier
Your problemReach 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: the At (full CRUD) and Ixed (read/update) type classes
"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"Compose: outerLens.andThen(innerLens) (Profunctor Optics)
"The two shapes hold the same information"An Iso, then compose: Iso >>> Lens = Lens
"One-way conversion inside an effectful pipeline"optic.dimap(...) (Profunctor Optics)
"Match a value by a predicate, not by type"Prisms.nearly

Key Takeaways

  • Direction first, cardinality second. The tree asks what you are doing to the focus, then how many values it reaches; those two answers pick the optic between them.
  • The two zero-or-one optics are not interchangeable. An Affine reaches a value that may be absent; a Prism reaches a value that may be another variant, and can rebuild the structure from it.
  • Start on the Focus DSL and leave it only when forced. Failure, accumulation and effects move you to OpticOps; inspection and multi-mode execution move you to the Free Monad DSL.
  • The advanced features are constraint-shaped. Subset means filtered, position means indexed, wrong shape means an adapter.
  • These are entry points, not conclusions. Every leaf here has a page; the trees route, the pages decide.

See Also


Previous: Production Readiness Next: Mapping at the Boundary