Traversal
A Traversal
is the generalisation of an Optional
to several targets. In other word, a Traversal
allows
to focus from a type S
into 0 to n values of type A
.
The most common example of a Traversal
would be to focus into all elements inside of a container (e.g. List
, Vector
, Option
).
To do this we will use the relation between the typeclass cats.Traverse
and Traversal
:
import monocle.Traversal
import cats.implicits._ // to get all cats instances including Traverse[List]
val xs = List(1,2,3,4,5)
val eachL = Traversal.fromTraverse[List, Int]
// eachL: Traversal[List[Int], Int] = monocle.PTraversal$$anon$2@2aa3c162
eachL.replace(0)(xs)
// res0: List[Int] = List(0, 0, 0, 0, 0)
eachL.modify(_ + 1)(xs)
// res1: List[Int] = List(2, 3, 4, 5, 6)
A Traversal
is also a Fold
, so we have access to a few interesting methods to query our data:
eachL.getAll(xs)
// res2: List[Int] = List(1, 2, 3, 4, 5)
eachL.headOption(xs)
// res3: Option[Int] = Some(value = 1)
eachL.find(_ > 3)(xs)
// res4: Option[Int] = Some(value = 4)
eachL.all(_ % 2 == 0)(xs)
// res5: Boolean = false
Traversal
also offers smart constructors to build a Traversal
for a fixed number of target (currently 2 to 6 targets):
case class Point(id: String, x: Int, y: Int)
val points = Traversal.apply2[Point, Int](_.x, _.y)((x, y, p) => p.copy(x = x, y = y))
points.replace(5)(Point("bottom-left",0,0))
// res6: Point = Point(id = "bottom-left", x = 5, y = 5)
Finally, if you want to build something more custom you will have to implement a Traversal
manually.
A Traversal
is defined by a single method modifyA
which corresponds to the Van Laarhoven representation.
For example, let's write a Traversal
for Map
that will focus into all values where the key satisfies a certain predicate:
import monocle.Traversal
import cats.Applicative
import alleycats.std.map._ // to get Traverse instance for Map (SortedMap does not require this import)
def filterKey[K, V](predicate: K => Boolean): Traversal[Map[K, V], V] =
new Traversal[Map[K, V], V]{
def modifyA[F[_]: Applicative](f: V => F[V])(s: Map[K, V]): F[Map[K, V]] =
s.map{ case (k, v) =>
k -> (if(predicate(k)) f(v) else v.pure[F])
}.sequence
}
val m = Map(1 -> "one", 2 -> "two", 3 -> "three", 4 -> "Four")
val filterEven = filterKey[Int, String](_ % 2 == 0)
// filterEven: Traversal[Map[Int, String], String] = repl.MdocSession$MdocApp$$anon$1@6811fa19
filterEven.modify(_.toUpperCase)(m)
// res7: Map[Int, String] = Map(
// 1 -> "one",
// 2 -> "TWO",
// 3 -> "three",
// 4 -> "FOUR"
// )
Laws
A Traversal
must satisfy all properties defined in TraversalLaws
from the core
module.
You can check the validity of your own Traversal
using TraversalTests
from the law
module.
In particular, a Traversal
must respect the modifyGetAll
law which checks that you can modify all elements targeted by a Traversal
def modifyGetAll[S, A](t: Traversal[S, A], s: S, f: A => A): Boolean =
t.getAll(t.modify(f)(s)) == t.getAll(s).map(f)
Another important law is composeModify
also known as fusion
law:
def composeModify[S, A](t: Traversal[S, A], s: S, f: A => A, g: A => A): Boolean =
t.modify(g)(t.modify(f)(s)) == t.modify(g compose f)(s)