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) should be(res0)
head.nonEmpty(ys) should be(res1)
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(1)
head.getOrModify(ys)
// res3: Either[List[Int],Int] = Left(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) should be(res0)
head.set(5)(xs) should be(res1)
head.getOption(ys) should be(res2)
head.set(5)(ys) should be(res3)
We can also modify the target of Optional
with a function:
head.modify(_ + 1)(xs) should be(res0)
head.modify(_ + 1)(ys) should be(res1)
Or use modifyOption
/ setOption
to know if the update was successful:
head.modifyOption(_ + 1)(xs) should be(res0)
head.modifyOption(_ + 1)(ys) should be(res1)
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.
val head = Optional[List[Int], Int] {
case Nil => None
case x :: xs => Some(x)
} { a =>
{
case Nil => Nil
case x :: xs => a :: xs
}
}
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)
}
new OptionalLaws(head).getOptionSet(List(1, 2, 3)) should be(res0)
new OptionalLaws(head).setGetOption(List(1, 2, 3), 20) should be(res1)