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?
- If you want to understand what a carrier is and why one trait backs every optic family, read Concepts.
- If you want the per-family tour with the kind of optic you'd reach for in each situation, read Optics reference.
- If you're coming from Monocle, jump to Migrating from Monocle.
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.