Optional

An Optional is an Optic used to zoom inside a Product, e.g. case class, Tuple, HList or even Map. Unlike the Lens, the element that the Optional focuses on may not exist.

Optionals have two type parameters generally called S and A: Optional[S, A] where S represents the Product and A an optional element inside of S.

Let’s take a simple list with integers.

We can create an Optional[List[Int], Int] which zooms from a List[Int] to its potential head by supplying a pair of functions:

  • getOption: List[Int] => Option[Int]
  • set: Int => List[Int] => List[Int]
import monocle.Optional
val head = Optional[List[Int], Int] {
  case Nil => None
  case x :: xs => Some(x)
}{ a => {
   case Nil => Nil
   case x :: xs => a :: xs
  }
}

Once we have an Optional, we can use the supplied nonEmpty function to know if it matches:

val xs = List(1, 2, 3)
val ys = List.empty[Int]
head.nonEmpty(xs)
// res0: Boolean = true
head.nonEmpty(ys)
// res1: Boolean = false

We can use the supplied getOrModify function to retrieve the target if it matches, or the original value:

head.getOrModify(xs)
// res2: Either[List[Int], Int] = Right(value = 1)
head.getOrModify(ys)
// res3: Either[List[Int], Int] = Left(value = List())

The function getOrModify is mostly used for polymorphic optics. If you use monomorphic optics, use function getOption

We can use the supplied getOption and set functions:

head.getOption(xs)
// res4: Option[Int] = Some(value = 1)
head.set(5)(xs)
// res5: List[Int] = List(5, 2, 3)

head.getOption(ys)
// res6: Option[Int] = None
head.set(5)(ys)
// res7: List[Int] = List()

We can also modify the target of Optional with a function:

head.modify(_ + 1)(xs)
// res8: List[Int] = List(2, 2, 3)
head.modify(_ + 1)(ys)
// res9: List[Int] = List()

Or use modifyOption / setOption to know if the update was successful:

head.modifyOption(_ + 1)(xs)
// res10: Option[List[Int]] = Some(value = List(2, 2, 3))
head.modifyOption(_ + 1)(ys)
// res11: Option[List[Int]] = None

Laws

class OptionalLaws[S, A](optional: Optional[S, A]) {

  def getOptionSet(s: S): Boolean =
    optional.getOrModify(s).fold(identity, optional.set(_)(s)) == s

  def setGetOption(s: S, a: A): Boolean =
    optional.getOption(optional.set(a)(s)) == optional.getOption(s).map(_ => a)

}

An Optional must satisfy all properties defined in OptionalLaws in core module. You can check the validity of your own Optional using OptionalTests in law module.

getOptionSet states that if you getOrModify a value A from S and then set it back in, the result is an object identical to the original one.

setGetOption states that if you set a value, you always getOption the same value back.