Unsafe module
The unsafe
module contains Optics
that do not fully satisfy the set of Optics
Laws
of the core
module.
These Optics
hence require additional care from the end user to avoid unlawful usages as those are not enforced by the library.
The module currently defines one Optic
, UnsafeSelect
, but more will be added as required.
UnsafeSelect
UnsafeSelect
allows to create a Prism
based on a predicate. Let’s have a look at a simple example:
case class Person(name: String, age: Int)
Using an UnsafeSelect
we can select all Person
with age >= 18
and then use a Lens
to modify their name:
import monocle.unsafe.UnsafeSelect
import monocle.macros.GenLens
(UnsafeSelect.unsafeSelect[Person](_.age >= 18) composeLens GenLens[Person](_.name)).modify("Adult " + _)
This operator is considered unsafe because it allows for inconsistency if a Lens
is then used to change one of the values used in the predicates. For example:
import monocle.unsafe.UnsafeSelect
import monocle.macros.GenLens
(UnsafeSelect.unsafeSelect[Person](_.age >= 18) composeLens GenLens[Person](_.age)).set(0)
In this example the age is reset to 0
which invalidates the original predicate of age >= 18
. More formally UnsafeSelect
can invalidate the roundTripOtherWayLaw
law.
MapTraversal
MapTraversal
provides an Iso
allKeyValues
between Map[K,V]
and List([K, V])
and Traversal mapKVTraversal
of Map[K, V]
to (K, V)
. They are useful for traversing and modifying the entries of a map.
Both of them are unsafe because of key collision and the unorderness of map entries. As a rule of thumb, laws regarding modifying (K, V)
and then getting back a List[(K, V)]
could be broken, while laws regarding modifying (K, V)
and then getting back a Map[K, V]
could still hold. For example,
import monocle.unsafe.MapTraversal.allKeyValues
val list = List((1, "foo"), (1, "bar"))
val list2 = (allKeyValues[Int, String].get _ compose allKeyValues[Int, String].reverseGet)(list)
list
and list2
does not equal here due to key collision. Even if there is no key collision, the output list is still not guaranteed to be the original list as the order of map entries may be unspecified.
On the other hand, the composition of reverseGet
and get
is identity.
import monocle.unsafe.MapTraversal.allKeyValues
val map = Map(1 -> "foo", 1 -> "bar")
val map2 = (allKeyValues[Int, String].reverseGet _ compose allKeyValues[Int, String].get)(map)
Here is an example of how to modify the keys of a map with Monocle. Keep in mind creating identical keys can result in surprising behaviour.
import cats.implicits._
import monocle.Traversal
import monocle.unsafe.MapTraversal.allKeyValues
val eachL = Traversal.fromTraverse[List, (Int, String)]
def f(x: (Int, String)): (Int, String) = (x._1+1, x._2)
val m = Map(1 -> "foo", 2 -> "bar")
val l = allKeyValues[Int, String].get(m)
val l2 = eachL.modify(f)(l)
val m2 = allKeyValues[Int, String].reverseGet(l2)