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