cats-eo
An existential optics library for Scala 3, built on cats.
One Optic[S, T, A, B, F] trait, parameterised over a carrier
F[_, _], unifies every optic family. Composition crosses
families through Composer[F, G] bridges rather than N² hand-
written .andThen overloads.
Current version: 0.1.
60-second example
import eo.optics.Optic.*
import eo.generics.lens
import eo.docs.{Address, Person, Zip}
val street =
lens[Person](_.address)
.andThen(lens[Address](_.street))
val alice = Person("Alice", Address("Main St", Zip(12345, "6789")))
// alice: Person = Person(
// name = "Alice",
// address = Address(
// street = "Main St",
// zip = Zip(code = 12345, extension = "6789")
// )
// )
street.get(alice)
// res0: String = "Main St"
street.replace("Broadway")(alice)
// res1: Person = Person(
// name = "Alice",
// address = Address(
// street = "Broadway",
// zip = Zip(code = 12345, extension = "6789")
// )
// )
street.modify(_.toUpperCase)(alice)
// res2: Person = Person(
// name = "Alice",
// address = Address(
// street = "MAIN ST",
// zip = Zip(code = 12345, extension = "6789")
// )
// )
No .copy chains, no setter lambdas, no GenLens boilerplate. The
lens macro works on plain case classes, Scala 3 enums, and union
types alike.
Keep reading
- Getting started — install + the 60-second tour expanded.
- Concepts — what an Optic is, what a carrier is,
and how
Composerbridges family boundaries. - Optics reference — one section per family, with runnable examples.
- Generics — the
lens[S](_.field)andprism[S, A]macros, backed by Hearth. - Circe integration —
JsonPrism/JsonTraversal, cursor-backed navigation into circeJsonwith no full decode. - Cookbook — common patterns: option fields, composed Lens/Optional, multi-focus modify, JSON path edits.
- Migrating from Monocle — a translation table + where EO diverges.