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]
replace: 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 replace
functions:
head.getOption(xs)
// res4: Option[Int] = Some(value = 1)
head.replace(5)(xs)
// res5: List[Int] = List(5, 2, 3)
head.getOption(ys)
// res6: Option[Int] = None
head.replace(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
/ replaceOption
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.replace(_)(s)) == s
def setGetOption(s: S, a: A): Boolean =
optional.getOption(optional.replace(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 replace
it back in, the result is an object identical to the original one.
setGetOption
states that if you replace
a value, you always getOption
the same value back.