Getting started

Install

Add the module(s) you need to your build.sbt:

libraryDependencies ++= Seq(
  "dev.constructive" %% "cats-eo"          % "0.1",
  "dev.constructive" %% "cats-eo-generics" % "0.1",  // optional: macro-derived optics
  "dev.constructive" %% "cats-eo-circe"    % "0.1",  // optional: JSON optics
  "dev.constructive" %% "cats-eo-laws"     % "0.1" % Test,
)

Requires Scala 3.8.x on JDK 17 or JDK 21.

First example

import eo.optics.Optic.*
import eo.generics.lens
import eo.docs.{Address, Person, Zip}

A Lens focuses a single field of a product:

val nameL = lens[Person](_.name)
// nameL: SimpleLens[Person, String, NamedTuple[*:["address", EmptyTuple], *:[Address, EmptyTuple]]] = eo.optics.SimpleLens@247f9220

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")
//   )
// )

nameL.get(alice)
// res0: String = "Alice"
nameL.modify(_.toUpperCase)(alice)
// res1: Person = Person(
//   name = "ALICE",
//   address = Address(
//     street = "Main St",
//     zip = Zip(code = 12345, extension = "6789")
//   )
// )

Compose two lenses with .andThen — the result is another Lens into the composite path:

val streetL =
  lens[Person](_.address)
    .andThen(lens[Address](_.street))
// streetL: Optic[Person, Person, String, String, [T1 >: Nothing <: Any, T2 >: Nothing <: Any] =>> Tuple2[T1, T2]] = eo.optics.Optic$$anon$3@3ec68927

streetL.get(alice)
// res2: String = "Main St"
streetL.replace("Broadway")(alice)
// res3: Person = Person(
//   name = "Alice",
//   address = Address(
//     street = "Broadway",
//     zip = Zip(code = 12345, extension = "6789")
//   )
// )
streetL.modify(_.toUpperCase)(alice)
// res4: Person = Person(
//   name = "Alice",
//   address = Address(
//     street = "MAIN ST",
//     zip = Zip(code = 12345, extension = "6789")
//   )
// )

No .copy chains; the lens macro generates the setter for you.

What next?

Development commands

For contributors working on the library itself:

sbt compile                                          # full build
sbt test                                             # core + tests + generics + circe
sbt "~compile"                                       # watch compile
sbt docs/mdoc                                        # compile site's code fences
sbt docs/tlSitePreview                               # preview the site locally
scalafmt && scalafmt --check                         # format + verify
sbt "clean; coverage; tests/test; coverageReport"    # coverage report

Benchmarks live outside the root aggregator — see benchmarks/README.md for run instructions.