Monocle's optionals have the following access functions (for Optional[C,A]
):
getOption: C => Option[A]
set: A => C => C
This is at odds with the original definition of (partial) asymmetric data lenses. I would expect:
getOption: C => Option[A]
setOption: A => C => Option[C]
What is the reason for that? How do I obtain classic partial lenses with Monocle? When programming lenses I found that it much more problematic to ensure totality of set than of get ...
Consider the following partial lens for looking up values in a list by index (note that this is just a pedagogical example, since monocle.std.list.listIndex
provides this functionality off the shelf):
import monocle.Optional
def listIndexOptional[A](i: Int): Optional[List[A], A] =
Optional[List[A], A](_.lift(i))(a => l =>
if (l.isDefinedAt(i)) l.updated(i, a) else l
)
Now we can define an Optional
that points to the third item in a list of strings:
val thirdString = listIndexOptional[String](2)
And use it like this:
scala> thirdString.set("0")(List("a", "b", "c"))
res4: List[String] = List(a, b, 0)
scala> thirdString.set("0")(List("a", "b"))
res5: List[String] = List(a, b)
Note that if there is no third item, the operation just returns the list unmodified. If we wanted to know whether the item had been updated, we can use setOption
:
scala> thirdString.setOption("0")(List("a", "b", "c"))
res6: Option[List[String]] = Some(List(a, b, 0))
scala> thirdString.setOption("0")(List("a", "b"))
res7: Option[List[String]] = None
The fact that the Optional.apply
method takes as its second argument a function A => S => S
is partly a convenience, since we often want to define partial lenses in this way, and partly so that we can't define a partial lens where getOption
and setOption
disagree on whether the target exists.
If you really want to, you can always define an Optional
in terms of a A => S => Option[S]
setter by tacking a getOrElse(s)
on the end.