Wander
The Wander
class extends Strong and Choice, and it is used to define Traversals.
trait Wander[P[_, _]] extends Strong[P] with Choice[P] {
def wander[S, T, A, B](traversal: Traversing[S, T, A, B])(pab: P[A, B]): P[S, T]
}
It defines a wander
method which takes a Traversing[S, T, A, B]
and P[A, B]
and returns P[S, T]
Traversing
import cats.Applicative
trait Traversing[S, T, A, B] {
def apply[F[_]](f: A => F[B])(s: S)(implicit ev: Applicative[F]): F[T]
}
In order to understand Traversing[S, T, A, B]
, we should look at a simpler construct, the over
method.
The over
method lets us modify the focus/foci using a function. If we define a polymorphic Traversal
, or any kind of
polymorphic Optic
that supports modifications, the over
method's return type will be different from the initial structure. The initial structure S
will become, T
def over(f: A => B): S => T
Consider a polymorphic Traversal
of structure (Int, Int)
with a focus of the second element of the tuple Int
and a modified focus of String
, the modified
structure will be (Int, String)
S ~ (Int, Int) // initial structure
T ~ (Int, String) // modified structure
A ~ Int // initial focus
B ~ String // modified focus
import proptics.Traversal_
// import proptics.Traversal_
val traversal: Traversal_[(Int, Int), (Int, String), Int, String] =
Traversal_[(Int, Int), (Int, String), Int, String](_._2) { case (i, _) => str => (i, str) }
// traversal: Traversal_[(Int, Int),(Int, String),Int,String] = proptics.Traversal_$$anon$13@2859e95
val initialStructure: (Int, Int) = (9, 10)
// initialStructure: (Int, Int) = (9,10)
val modifiedStructure: (Int, String) = traversal.over(_ => "Hello")(initialStructure)
// modifiedStructure: (Int, String) = (9,Hello)
Traversing
's apply method is similar to over
but takes A -> F[B]
instead of A -> B
and the return type is F[T]
instead of T
.
Basically Traversing
is an over
method that lifts the value to an Applicative
context.
def over(f: A => B): S => T
def apply[F[_]](f: A => F[B])(s: S)(implicit ev: Applicative[F]): F[T]
So if we want to implement over
using Wander
we have to take into consideration Traversing
too.
Wander
takes a Traversing[S, T, A, B]
and P[A, B]
and returns P[S, T]
. Traversing
deals with lifted types and returns F[T]
,
so the types are not aligned with each other, Therefore, we need to peek the Function
type as our P[_, _]
and an F[_]
for Traversing
such that we can "peel" of the
F[_]
from the return type of f: A => F[B]
and the return type F[T]
, and that is exactly what Cats.Id
does.
Function as Wander profunctor
import cats.Id
// import cats.Id
import proptics.profunctor.{Traversing, Wander}
// import proptics.profunctor.{Traversing, Wander}
implicit final def wanderFunction: Wander[Function] = new Wander[Function] {
override def wander[S, T, A, B](traversing: Traversing[S, T, A, B])(pab: A => B): S => T =
traversing[Id](pab)
}
// wanderFunction: Wander[Function]
If we curry the wander
method, then it would take the form of:
Traversing[S, T, A, B] => (A => B) => S => T
. We can think of it as
if we get an argument of Traversing[S, T, A, B]
then we would return a function that when given an A => B
returns an S => T
,
(A => B) => S => T
which is equivalent to the over
method.