ATraversal
ATraversal is similar to a Traversal, but has different internal encodings, it is used
to focus on zero, one, or many values. ATraversal is usually used for collections like List, Map, Array.
ATraversal internal encoding
Polymorphic ATraversal
ATraversal_[S, T, A, B]
ATraversal_[S, T, A, B] is a function P[A, B] => P[S, T] where's the P[_, _] is a data type of Bazaar, thus making
it a function Bazaar[* => *, A, B, A, B] => Bazaar[* => *, A, B, S, T].
/**
* @tparam S the source of an ATraversal_
* @tparam T the modified source of an ATraversal_
* @tparam A the focus of an ATraversal_
* @tparam B the modified focus of an ATraversal_
*/
abstract class ATraversal_[S, T, A, B] {
def apply(bazaar: Bazaar[Function, A, B, A, B]): Bazaar[Function, A, B, S, T]
}
ATraversal_[S, T, A, B] changes its foci from A to B, resulting in a change of structure from S to T.
An ATraversal that changes its foci/structure, is called Polymorphic ATraversal.
Monomorphic ATraversal
ATraversal[S, A]
ATraversal[S, A] is a type alias for ATraversal_[S, S, A, A], which has the same type of foci A, thus preserving the same type of structure S.
type ATraversal[S, A] = ATraversal_[S, S, A, A]
An ATraversal[S, A] means that the type S might contain zero or one value of type A.
An ATraversal that does not change its foci/structure, is called Monomorphic ATraversal.
Constructing ATraversal
ATraversal_[S, T, A, B] is constructed using the ATraversal_[S, T, A, B]#apply function.
For a given ATraversal_[S, T, A, B] it takes two functions as arguments,
view: S => A which is a getter function, that produces zero, one, or many elements of A given an S, and set: S => B => T function which takes a structure S and a new focus B and returns
a structure of T filled will all foci of that B
object ATraversal_ {
def apply[S, T, A, B](get: S => A)(set: S => B => T): ATraversal_[S, T, A, B]
}
ATraversal[S, A] is constructed using the ATraversal[S, A]#apply function.
For a given ATraversal[S, A] it takes two functions as arguments, view: S => A which is a getter function, that produces zero, one, or many elements of A given an S, and set: S => A => S function which takes a structure S and a focus A and returns a
new structure S filled will all foci of that A.
object ATraversal {
def apply[S, A](get: S => A)(set: S => A => S): ATraversal[S, A]
}
Most of the time we will be dealing with collections. This is the way to create a ATraversal[S, A] for a collection:
import cats.syntax.option._
// import cats.syntax.option._ // (none, some) functions
import cats.syntax.eq._ // triple equals (===)
// import cats.syntax.eq._
import proptics.ATraversal
// import proptics.ATraversal
val list: List[Int] = List.range(1, 5)
// list: List[Int] = List(1, 2, 3, 4)
val optionByPredicate: Int => Option[Int] = i => if (i % 2 === 0) i.some else none[Int]
// optionByPredicate: Int => Option[Int] = $Lambda$12694/0x0000000802bfc3e0@5f962a8b
val listTraversal: ATraversal[List[Int], Int] = ATraversal.fromTraverse[List, Int]
// listTraversal: proptics.ATraversal[List[Int],Int] = proptics.ATraversal_$$anon$12@4e2da5c7
Common functions of a ATraversal
viewAll
listTraversal.viewAll(list)
// res0: List[Int] = List(1, 2, 3, 4)
set
listTraversal.set(list)
// res1: List[Int] = List(9, 9, 9, 9)
preview/first
listTraversal.preview(list)
// res2: Option[Int] = Some(1)
// synonym for preview
listTraversal.first(list)
// res3: Option[Int] = Some(1)
over
listTraversal.over(_ + 1)(list)
// res4: List[Int] = List(2, 3, 4, 5)
traverse
import cats.instances.option._ // summons an Applicative[Option] instance
// import cats.instances.option._
listTraversal.traverse(list)(optionByPredicate)
// res5: Option[List[Int]] = None
listTraversal.traverse(list)(_.some)
// res6: Option[List[Int]] = Some(List(1, 2, 3, 4))
foldMap
import cats.instances.int._ // summons a Semigroup[Int] instance
// import cats.instances.int._
import cats.instances.option._ // summons a Monoid[Option[Int]] instance
// import cats.instances.option._
listTraversal.foldMap(list)(optionByPredicate)
// res7: Option[Int] = Some(6)
foldRight
listTraversal.foldRight(list)(List.empty[Int])(_ :: _)
// res8: List[Int] = List(1, 2, 3, 4)
foldLeft
listTraversal.foldLeft(list)(List.empty[Int])((b, a) => a :: b)
// res9: List[Int] = List(4, 3, 2, 1)
forall
listTraversal.forall(_ < 9)(list)
// res10: Boolean = true
exists
listTraversal.exists(_ < 9)(list)
// res11: Boolean = true
contains
listTraversal.contains(9)(list)
// res12: Boolean = false
isEmpty
listTraversal.isEmpty(list)
// res13: Boolean = false
find
listTraversal.find(_ === 9)(list)
// res14: Option[Int] = None
last
listTraversal.last(list)
// res15: Option[Int] = Some(4)
minimum
listTraversal.minimum(list)
// res16: Option[Int] = Some(1)
maximum
listTraversal.maximum(list)
// res17: Option[Int] = Some(4)
Syntax
We can take advantage of the syntax package for ATraversal.
If we create a polymorphic ATraversal_[F[G[A]], F[A], G[A], A] such that the source would be a nested structure of F[G[A]] and the initial foci G[A] would have an Applicative[G] instance, and the modified foci would be an A , then we could
flip types F[_] and G[_] from F[G[A]] to G[F[A]] using the sequence method.
Consider F[_] to be a List, G[_] to be an Option and A to be an Int, the ATraversal would be:
ATraversal_[List[Option[Int]], List[Int], Option[Int], Int]
import cats.syntax.eq._
// import cats.syntax.eq._
import cats.syntax.option._
// import cats.syntax.option._
import cats.instances.list._
// import cats.instances.list._
import proptics.ATraversal_
// import proptics.ATraversal_
import proptics.syntax.aTraversal._
// import proptics.syntax.aTraversal._
val list: List[Int] = List.range(1, 5)
// list: List[Int] = List(1, 2, 3, 4)
val optionByPredicate: Int => Option[Int] = i => if (i % 2 === 0) i.some else none[Int]
// optionByPredicate: Int => Option[Int] = $Lambda$34954/0x0000000802317840@5d5007af
val listOfOptions: List[Option[Int]] = list.map(optionByPredicate)
// listOfOptions: List[Option[Int]] = List(None, Some(2), None, Some(4))
val listOfSomeOptions: List[Option[Int]] = list.map(_.some)
// listOfSomeOptions: List[Option[Int]] = List(Some(1), Some(2), Some(3), Some(4))
val traversal: ATraversal_[List[Option[Int]], List[Int], Option[Int], Int] =
ATraversal_.fromTraverse[List, Option[Int], Int]
// traversal: ATraversal_[List[Option[Int]],List[Int],Option[Int],Int] = ATraversal_$$anon$12@65892367
sequence
traversal.sequence(listOfOptions)
// res0: Option[List[Int]] = None
traversal.sequence(listOfSomeOptions)
// res1: Option[List[Int]] = Some(List(1, 2, 3, 4))
Exporting Bazaar as data type of ATraversal
ATraversal allows us to export its internal construction logic to a Bazaar using the toBazaar method.
val traversal: ATraversal[(Int, String), String] =
ATraversal[(Int, String), String](_._2) { case (i, _) => s => (i, s) }
// traversal: proptics.ATraversal[(Int, String),String] = proptics.ATraversal_$$anon$22@7218cbb6
val bazaar = traversal.toBazaar
// bazaar: internal.Bazaar[[α$11$, β$12$]α$11$ => β$12$,String,String,(Int, String),(Int, String)] =
// proptics.ATraversal_$$anon$22$$anon$23@5f364bc2
We can later on create a new instance of an ATraversal or a Traversal from the bazaar instance
import proptics.ATraversal
// import proptics.ATraversal_
import proptics.Traversal
// import proptics.Traversal
val aTraversalFromBazaar: ATraversal[(Int, String), String] = ATraversal.fromBazaar(bazaar)
// aTraversalFromBazaar: proptics.ATraversal[(Int, String),String] =
// proptics.ATraversal_$$anon$19@43bf1ac9
val traversalFromBazaar: Traversal[(Int, String), String] = Traversal.fromBazaar(bazaar)
// traversalFromBazaar: proptics.Traversal[(Int, String),String] =
// proptics.Traversal_$$anon$12@7494feef
Laws
A Traversal must satisfy all ATraversalLaws. These laws reside in the <a href="../../api/proptics/law/>proptics.law package.
import cats.instances.list._
// import cats.instances.list._
import cats.syntax.eq._
// import cats.syntax.eq._
import cats.{Applicative, Eq}
// import cats.{Applicative, Eq}
import proptics.ATraversal
// import proptics.ATraversal
Traversing with "empty" handler shouldn't change anything
def respectPurity[F[_]: Applicative, S, A](traversal: ATraversal[S, A], s: S)
(implicit ev: Eq[F[S]]): Boolean =
traversal.traverse[F](s)(Applicative[F].pure _) === Applicative[F].pure(s)
val listTraversal = ATraversal.fromTraverse[List, Int]
// listTraversal: proptics.ATraversal[List[Int],Int] = proptics.ATraversal_$$anon$12@4fb75a8c
respectPurity(listTraversal, List.range(1, 5))
// res0: Boolean = true
Running twice with different handlers is equivalent to running it once with the composition of those handlers
def consistentFoci[S: Eq, A](traversal: ATraversal[S, A], s: S, f: A => A, g: A => A): Boolean =
(traversal.over(f) compose traversal.over(g))(s) === traversal.over(f compose g)(s)
consistentFoci[List[Int], Int](listTraversal, List.range(1, 5), _ + 1, _ * 2)
// res1: Boolean = true