scalalensesmonocle-scala

Are Monocle's Optionals the same as partial Lenses?


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


Solution

  • 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.