AffineTraversal
An AffineTraversal
is similar to a Traversal that may contain zero or one element.
It is a combination of a Lens and a Prism.
Constructing a monomorphic AffineTraversal
Consider a Json ADT/Sum type
sealed trait Json
case object JNull extends Json
case class JString(value: String) extends Json
case class JNumber(value: Double) extends Json
val jNumber: Json = JNumber(9)
Using companion object
AffineTraversal[S, A]
is constructed using the AffineTraversal[S, A]
#apply function.
For a given AffineTraversal[S, A]
it takes two functions as arguments, viewOrModify: S => Either[S, A]
which is
a matching function that produces an Either[S, A]
given an S
, and set: S => A => S
function which takes a
structure S
and a focus A
and returns a new structure of S
.
object AffineTraversal {
def apply[S, A](viewOrModify: S => Either[S, A])(set: S => A => S): AffineTraversal[S, A]
}
We can define an AffineTraversal
which focuses on JNumber
import cats.syntax.either._
import proptics.AffineTraversal
def viewOrModify(json: Json): Either[Json, Double] = json match {
case JNumber(value) => value.asRight[Json]
case json => json.asLeft[Double]
}
def setJson(json: Json): Double => Json = (i: Double) =>
json match {
case JNumber(_) => JNumber(i)
case _ => json
}
val jsonAffineTraversal: AffineTraversal[Json, Double] =
AffineTraversal(viewOrModify)(setJson)
jsonAffineTraversal.viewOrModify(jNumber)
// val res0: Either[Json,Double] = Right(9.0)
jsonAffineTraversal.viewOrModify(JNull)
// val res1: Either[Json,Double] = Left(JNull)
jsonAffineTraversal.set(10)(jNumber)
// val res2: : Json = JNumber(10.0)
jsonAffineTraversal.set(10)(JNull)
// val res3: : Json = JNull
Using fromPreview method
object AffineTraversal {
def fromPreview[S, A](preview: S => Option[A])(set: S => A => S): AffineTraversal[S, A]
}
import proptics.AffineTraversal
import cats.syntax.option._
def preview(json: Json): Option[Double] = json match {
case JNumber(value) => value.some
case _ => None
}
def setJson(json: Json): Double => Json = (i: Double) =>
json match {
case JNumber(_) => JNumber(i)
case _ => json
}
val jsonAffineTraversal: AffineTraversal[Json, Double] =
AffineTraversal.fromPreview(preview)(setJson)
Using fromPartial method
object AffineTraversal {
def fromPartial[S, A](preview: PartialFunction[S, A])(set: S => A => S): AffineTraversal[S, A]
}
import proptics.AffineTraversal
def setJson(json: Json): Double => Json = (i: Double) =>
json match {
case JNumber(_) => JNumber(i)
case _ => json
}
val jsonAffineTraversal =
AffineTraversal.fromPartial[Json, Double] { case JNumber(value) => value }(setJson)
Constructing a polymorphic AffineTraversal
AffineTraversal_[S, T, A, B]
is constructed using the
AffineTraversal_[S, T, A, B]#apply function.
For a given AffineTraversal_[S, T, A, B]
it takes two functions as arguments, viewOrModify: S => Either[T, A]
which is a matching function that produces an Either[T, A]
given an S
, and set: S => B => T
function which takes a
structure S
and a focus B
and returns a structure of T
.
object AffineTraversal_ {
def apply[S, T, A, B](viewOrModify: S => Either[T, A])(set: S => B => T): AffineTraversal_[S, T, A, B]
}
Methods
viewOrModify
/** view the focus or return the modified source of an AffineTraversal */
def viewOrModify(s: S): Either[S, A]
jsonAffineTraversal.viewOrModify(jNumber)
// val res0: Either[Json,Double] = Right(9.0)
jsonAffineTraversal.viewOrModify(JNull)
// val res1: Either[Json,Double] = Left(JNull)
preview
/** view the focus of an AffineTraversal, if there is any */
def preview(s: S): Option[A]
jsonAffineTraversal.preview(JNumber(9.0))
// val res2: Option[Double] = Some(9.0)
jsonAffineTraversal.preview(JNull)
// val res3: Option[Double] = None
set
/** set the focus of an AffineTraversal */
def set(b: A): S => S
jsonAffineTraversal.set(9.0)(JNumber(1))
// val res4: Json = JNumber(9.0)
jsonAffineTraversal.set(1.0)(JNull)
// val res5: Json = JNull
setOption
/** set the focus of an AffineTraversal conditionally if it is not None */
def setOption(a: A): S => Option[S]
jsonAffineTraversal.setOption(9.0)(JNumber(1))
// val res6: Option[Json] = Some(JNumber(9.0))
jsonAffineTraversal.setOption(9)(JNull)
// val res7: Option[Request] = None
over
/** modify the focus type of an AffineTraversal using a function */
def over(f: A => A): S => S
jsonAffineTraversal.over(_ + 1)(JNumber(8))
// val res8: Json = JNumber(9.0)
jsonAffineTraversal.over(_ + 1)(JNull)
// val res9: Json = JNull
overOption
/** modify the focus of an AffineTraversal using a function conditionally if it is not None */
def overOption(f: A => A): S => Option[S]
jsonAffineTraversal.overOption(_ + 1)(JNumber(8))
// val res10: Option[Json] = Some(JNumber(9.0))
jsonAffineTraversal.overOption(_ + 1)(JNull)
// val res11: Option[Json] = None
traverse
/** modify the focus type of an AffineTraversal using a cats.Functor */
def traverse[F[_]](s: S)(f: A => F[A])(implicit arg0: Applicative[F]): F[S]
import cats.syntax.eq._
def powerOf2IfNumberOf3(i: Double): Option[Double] =
Option.when(i === 3.0)(Math.pow(i, 2))
jsonAffineTraversal.traverse[Option](jNumber)(powerOf2IfNumberOf3)
// val res12: Option[Json] = None
jsonAffineTraversal.traverse[Option](JNumber(3))(powerOf2IfNumberOf3)
// val res13: Option[Json] = Some(JNumber(9.0))
overF
/** synonym for [[traverse]], flipped */
def overF[F[_]](f: A => F[A])(s: S)(implicit arg0: Applicative[F]): F[S]
import cats.syntax.eq._
def powerOf2IfNumberOf3(i: Double): Option[Double] =
Option.when(i === 3)(Math.pow(i, 2))
val partialAffineTraversal = jsonAffineTraversal.overF[Option](powerOf2IfNumberOf3) _
partialAffineTraversal(jNumber)
// val res14: Option[Json] = None
partialAffineTraversal(JNumber(3.0))
// val res15: Option[Json] = Some(JNumber(9.0))
exists
/** test whether a predicate holds for the focus of an AffineTraversal */
def exists(f: A => Boolean): S => Boolean
import cats.syntax.eq._
jsonAffineTraversal.exists(_ === 9.0)(jNumber)
// val res16: Boolean = true
notExists
/** test whether a predicate does not hold for the focus of an AffineTraversal */
def notExists(f: A => Boolean): S => Boolean
jsonAffineTraversal.notExists(_ === 9.0)(jNumber)
// val res17: Boolean = false
contains
/** test whether the focus of an AffineTraversal contains a given value */
def contains(a: A)(s: S)(implicit ev: Eq[A]): Boolean
jsonAffineTraversal.contains(9.0)(jNumber)
// val res18: Boolean = true
notContains
/** test whether the focus of an AffineTraversal does not contain a given value */
def notContains(a: A)(s: S)(implicit ev: Eq[A]): Boolean
jsonAffineTraversal.notContains(9)(jNumber)
// val res19: Boolean = false
isEmpty
/** check if the AffineTraversal does not contain a focus */
def isEmpty(s: S): Boolean
jsonAffineTraversal.isEmpty(jNumber)
// val res20: Boolean = false
jsonAffineTraversal.isEmpty(JNull)
// val res21: Boolean = true
nonEmpty
/** check if the AffineTraversal contains a focus */
def nonEmpty(s: S): Boolean
jsonAffineTraversal.nonEmpty(jNumber)
// val res22: Boolean = true
jsonAffineTraversal.nonEmpty(JNull)
// val res23: Boolean = false
find
/** find the focus of an AffineTraversal that satisfies a predicate, if there is any */
def find(f: A => Boolean): S => Option[A]
import cats.syntax.eq._
jsonAffineTraversal.find(_ === 9.0)(jNumber)
// val res24: Option[Double] = Some(9.0)
jsonAffineTraversal.find(_ === 9)(JNull)
// val res25: Option[String] = None
failover
/** try to map a function over this AffineTraversal, failing if the AffineTraversal has no focus */
def failover[F[_]](f: A => B)
(s: S)
(implicit ev0: Choice[Star[(Disj[Boolean], *), *, *]],
ev1: Strong[Star[(Disj[Boolean], *), *, *]],
ev2: Alternative[F]): F[T]
import spire.implicits._
jsonAffineTraversal.failover[Option](_ + 1)(JNumber(8))
// val res26: Option[Json] = Some(JNumber(9.0))
jsonAffineTraversal.failover[Option](_ + 1)(JNull)
// val res27: Option[Request] = None
forall
/** test whether there is no focus or a predicate holds for the focus of an AffineTraversal */
def forall(f: A => Boolean): S => Boolean
jsonAffineTraversal.forall(_ === 9.0)(jNumber)
// val res28: Boolean = true
jsonAffineTraversal.forall(_ === 1.0)(jNumber)
// val res29: Boolean = false
jsonAffineTraversal.forall(_ === 1.0)(JNull)
// val res30: Boolean = true
forall
/**
* test whether there is no focus or a predicate holds for the focus of
* an AffineTraversal, using Heyting algebra
*/
def forall[R](s: S)(f: A => R)(implicit arg0: Heyting[R]): R
import spire.std.boolean._
import cats.syntax.eq._
jsonAffineTraversal.forall(jNumber)(_ === 9.0)
// val res31: Boolean = true
jsonAffineTraversal.forall(jNumber)(_ === 1.0)
// val res32: Boolean = false
jsonAffineTraversal.forall(JNull)(_ === 1.0)
// val res33: Boolean = true
AffineTraversal internal encoding
Polymorphic AffineTraversal
AffineTraversal_[S, T, A, B]
AffineTraversal_[S, T, A, B]
is a function P[A, B] => P[S, T]
that takes a Choice and
a Strong of P[_, _].
/**
* @tparam S the source of an AffineTraversal_
* @tparam T the modified source of an AffineTraversal_
* @tparam A the focus of an AffineTraversal_
* @tparam B the modified focus of an AffineTraversal_
*/
abstract class AffineTraversal_[S, T, A, B] {
def apply[P[_, _]](pab: P[A, B])(implicit ev0: Choice[P], ev1: Strong[P]): P[S, T]
}
AffineTraversal_[S, T, A, B]
changes its focus from A
to B
, resulting in a change of structure from S
to T
.
An AffineTraversal
that changes its focus/structure, is called Polymorphic AffineTraversal
.
Monomorphic AffineTraversal
AffineTraversal[S, A]
AffineTraversal[S, A]
is a type alias for AffineTraversal_[S, S, A, A]
, which has the same type of focus A
, thus
preserving the same type of structure S
.
type AffineTraversal[S, A] = AffineTraversal_[S, S, A, A]
An AffineTraversal[S, A]
means that the type S
might contain zero or one value of type A
.
An AffineTraversal
that does not change its focus/structure, is called Monomorphic AffineTraversal
.
Laws
An AffineTraversal
must satisfy all AffineTraversalLaws.
These laws reside in the proptics.law package.
import cats.Eq
import cats.instances.option._
import Function.const
import cats.instances.string._
import cats.instances.int._
implicit val eqJson: Eq[Json] = Eq.instance[Json] { (x: Json, y: Json) =>
(x, y) match {
case (JNumber(v1), JNumber(v2)) => v1 === v2
case (JString(v1), JString(v2)) => v1 === v2
case (JNull, JNull) => true
case _ => false
}
}
What you get is what you set
def getSet[S: Eq, A](affineTraversal: AffineTraversal[S, A], s: S): Boolean =
affineTraversal.viewOrModify(s).fold(identity, affineTraversal.set(_)(s)) === s
getSet[Json, Double](jsonAffineTraversal, JNumber(9.0))
// val res0: Boolean = true
Option
of the same value
Set and then preview a value, will get you an def previewSet[S, A: Eq](affineTraversal: AffineTraversal[S, A], s: S, a: A): Boolean =
affineTraversal.preview(affineTraversal.set(a)(s)) === affineTraversal.preview(s).map(const(a))
previewSet[Json, Double](jsonAffineTraversal, JNumber(9.0), 1.0)
// val res1: Boolean = true
Setting twice is the same as setting once
def setSet[S: Eq, A](affineTraversal: AffineTraversal[S, A], s: S, a: A): Boolean =
affineTraversal.set(a)(affineTraversal.set(a)(s)) === affineTraversal.set(a)(s)
setSet[Json, Double](jsonAffineTraversal, JNumber(1.0), 9.0)
// val res2: Boolean = true