FAQ

Which imports are required to use typeclass based optics such as at, each, headOption?

All typeclasses are defined in monocle.function package, you can import optic individually with

import monocle.function.$TYPE_CLASS.$OPTIC

For example

import monocle.function.At.at
import monocle.function.Cons.{headOption, tailOption}

or you can import all typeclass based optics with

import monocle.function.all._

Here is a complete example

import monocle.function.all._
import monocle.macros.GenLens

case class Foo(s: String, is: List[Int])
val foo = Foo("Hello", List(1,2,3))

val is = GenLens[Foo](_.is)
(is composeOptional headOption).getOption(foo)
// res2: Option[Int] = Some(1)

Note: if you use a version of monocle before 1.4.x, you need another import to get the typeclass instance

import monocle.std.list._

What is the difference between at and index? When should I use one or the other?

Both at and index define indexed optics. However, at is a Lens and index is an Optional which means at is stronger than index. Let’s take the example of a Map

import monocle.Iso

val m = Map("one" -> 1, "two" -> 2)

val root = Iso.id[Map[String, Int]]
(root composeOptional index("two")).set(0)(m)   // update value at index "two"
// res5: Map[String,Int] = Map(one -> 1, two -> 0)

(root composeOptional index("three")).set(3)(m) // noop because m doesn't have a value at "three"
// res6: Map[String,Int] = Map(one -> 1, two -> 2)

(root composeLens at("three")).set(Some(3))(m)  // insert element at "three"
// res7: Map[String,Int] = Map(one -> 1, two -> 2, three -> 3)

(root composeLens at("two")).set(None)(m)       // delete element at "two"
// res8: Map[String,Int] = Map(one -> 1)

(root composeLens at("two")).set(Some(0))(m)    // upsert element at "two"
// res9: Map[String,Int] = Map(one -> 1, two -> 0)

In other words, index can update any existing values while at can also insert and delete.

Since index is weaker than at, we can implement an instance of Index on more data structure than At. For instance, List or Vector only have an instance of Index because there is no way to insert an element at an arbitrary index of a sequence.

Note: root is a trick to help type inference. Without it, we would get the following error

scala> index("two").set(0)(m)
<console>:25: error: type mismatch;
 found   : monocle.function.Index[Map[String,Nothing],String,Nothing]
 required: monocle.function.Index[Map[String,Nothing],String,A]
Note: Nothing <: A, but class Index is invariant in type A.
You may wish to define A as +A instead. (SLS 4.5)
       index("two").set(0)(m)
            ^

The problem is that the compiler does not have enough information to infer the correct Index instance. By using Iso.id[Map[String, Int]] as a prefix, we give a hint to the type inference saying we focus on a Map[String, Int]. Similarly, if the Map was in a case class, a Lens would provide the same kind of hint than Iso.id

case class Bar(kv: Map[String, Int])
(GenLens[Bar](_.kv) composeOptional index("two")).set(0)(Bar(m))
// res11: Bar = Bar(Map(one -> 1, two -> 0))