FAQ
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.index("two").replace(0)(m) // update value at index "two"
// res0: Map[String, Int] = Map("one" -> 1, "two" -> 0) // update value at index "two"
root.index("three").replace(3)(m) // noop because m doesn't have a value at "three"
// res1: Map[String, Int] = Map("one" -> 1, "two" -> 2) // noop because m doesn't have a value at "three"
root.at("three").replace(Some(3))(m) // insert element at "three"
// res2: Map[String, Int] = Map("one" -> 1, "two" -> 2, "three" -> 3) // insert element at "three"
root.at("two").replace(None)(m) // delete element at "two"
// res3: Map[String, Int] = Map("one" -> 1) // delete element at "two"
root.at("two").replace(Some(0))(m) // upsert element at "two"
// res4: 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
index("two").replace(0)(m)
// error: not found: value index
// index("two").replace(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])
import monocle.macros.GenLens
GenLens[Bar](_.kv).index("two").replace(0)(Bar(m))
// res6: Bar = Bar(kv = Map("one" -> 1, "two" -> 0))
