APrism
APrism
is similar to Prism, but has different internal encodings, it is
used to focus on one case of a sum type like Option
and Either
.
APrism internal encoding
Polymorphic APrism
APrism_[S, T, A, B]
APrism_[S, T, A, B]
is a function P[A, B] => P[S, T]
where's the P[_, _]
is a data type of Market, thus making
it a function Market[A, B, A, B] => Market[A, B, S, T]
.
/**
* @tparam S the source of an APrism_
* @tparam T the modified source of an APrism_
* @tparam A the focus of an APrism_
* @tparam B the modified focus of an APrism_
*/
abstract class APrism_[S, T, A, B] {
def apply(market: Market[A, B, A, B]): Market[A, B, S, T]
}
APrism_[S, T, A, B]
changes its focus from A
to B
, resulting in a change of structure from S
to T
.
An APrism
that changes its focus/structure, is called Polymorphic APrism
.
Monomorphic APrism
APrism[S, A]
APrism[S, A]
is a type alias for APrism_[S, S, A, A]
, which has the same type of focus A
, thus preserving the same type of structure S
.
type APrism[S, A] = APrism_[S, S, A, A]
APrism[S, A]
means that the type S
might contain a value of type A
.
An APrism
determines whether a single value matches some set of properties, therefore, is a natural candidate for pattern-matching semantics.
An APrism
that does not change its focus/structure, is called Monomorphic APrism
.
Constructing APrisms
APrism_[S, T, A, B]
is constructed using the APrism_[S, T, A, B]#apply function.
For a given APrism_[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 review: B => T
function which takes a focus of B
and returns a structure of T
.
object APrism_ {
def apply[S, T, A, B](viewOrModify: S => Either[T, A])(review: B => T): APrism_[S, T, A, B]
}
APrism[S, A]
is constructed using the APrism[S, A]#apply function. For a given APrism[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 review: A => S
function which takes a focus of A
and returns a new structure of S
.
object APrism {
def apply[S, A](viewOrModify: S => Either[S, A])(review: A => S): APrism[S, A]
}
Consider a Request ADT/Sum type
sealed trait Request
// defined trait Request
case object Pending extends Request
// defined object Pending
final case class Success(value: Int) extends Request
// defined class Success
final case class Error(reason: String) extends Request
// defined class Error
val request: successRequest = Success(200)
// successRequest: Request = Success(200)
We can define an APrism
which focuses on Success
request
import proptics.APrism
// import proptics.APrism
import cats.syntax.either._
// import cats.syntax.either._
val successRequestPrism: APrism[Request, Int] = APrism[Request, Int] {
case Success(value) => value.asRight[Request]
case req => req.asLeft[Int]
}(Success)
// successRequestPrism: proptics.APrism[Request,Int] = proptics.APrism_$$anon$13@be4228d
A more concise version would be using the fromPreview
method
object APrism {
def fromPreview[S, A](preview: S => Option[A])(review: A => S): APrism[S, A]
}
import proptics.APrism
// import proptics.APrism
import cats.syntax.option._
// import cats.syntax.option._
val successRequestPrism: APrism[Request, Int] = APrism.fromPreview[Request, Int] {
case Success(value) => value.some
case _ => None
}(Success)
// successRequestPrism: proptics.APrism[Request,Int] = proptics.APrism_$$anon$13@237ad392
An even more concise version would be using the fromPartial
method
object APrism {
def fromPartial[S, A](preview: PartialFunction[S, A])(review: A => S): APrism[S, A]
}
import proptics.APrism
// import proptics.APrism
import cats.syntax.eq._ // triple equals (===)
// import cats.syntax.eq._
val successRequestPrism: APrism[Request, Int] =
APrism.fromPartial[Request, Int] { case Success(value) => value }(Success)
// successRequestPrism: proptics.APrism[Request,Int] = proptics.APrism_$$anon$13@1fa5e3fb
Common functions of an APrism
viewOrModify
successRequestPrism.viewOrModify(successRequest)
// res0: Either[Request,Int] = Right(200)
preview
successRequestPrism.preview(successRequest)
// res1: Option[Int] = Some(200)
review
successRequestPrism.review(201)
// res2: Request = Success(201)
set
successRequestPrism.set(202)(successRequest)
// res3: : Request = Success(202)
successRequestPrism.set(202)(Pending)
// res4: Request = Pending
setOption
successRequestPrism.setOption(204)(successRequest)
// res5: Option[Request] = Some(Success(204))
successRequestPrism.setOption(204)(successRequest)
// res6: Option[Request] = None
over
val to204: Int => Int = _ + 4
// to204: Int => Int = $Lambda$12332/517676320@419cdca6
successRequestPrism.over(_ + 4)(successRequest)
// res7: Request = Success(204)
successRequestPrism.over(_ + 4)(Pending)
// res8: Request = Pending
traverse
val partialTraverse = successRequestPrism.traverse[Option](_: Request) {
case 200 => 200.some
case _ => None
}
// partialTraverse: Request => Option[Request] = $Lambda$12334/494843037@9d50a46
partialTraverse(successRequest)
// res9: Option[Request] = Some(Success(200))
partialTraverse(Success(204))
// res10: Option[Request] = None
forall
successRequestPrism.forall(_ === 204)(successRequest)
// res11: Boolean = false
successRequestPrism.forall(_ === 204)(Pending)
// res12: Boolean = true
successRequestPrism.forall(_ === 200)(successRequest)
// res13: Boolean = true
exists
successRequestPrism.exists(_ === 204)(successRequest)
// res14: Boolean = true
contains
successRequestPrism.contains(204)(successRequest)
// res15: Boolean = false
isEmpty
successRequestPrism.isEmpty(successRequest)
// res16: Boolean = false
successRequestPrism.isEmpty(Pending)
// res17: Boolean = true
find
successRequestPrism.find(_ === 200)(successRequest)
// res18: Option[Int] = Some(200)
successRequestPrism.find(_ === 204)(successRequest)
// res19: Option[Int] = None
Exporting Market as data type of APrism
APrism
allows us to export its internal construction logic to a Market
using the toMarket
method.
val successRequestPrism: APrism[Request, Int] =
APrism.fromPartial[Request, Int] { case Success(value) => value }(Success)
// successRequestPrism: proptics.APrism[Request,Int] = proptics.APrism_$$anon$13@1fa5e3fb
val market = successRequestPrism.toMarket
// market: proptics.internal.Market[Int,Int,Request,Request] =
// Market(proptics.APrism$$$Lambda$6201/0x0000000801d56840@71882c89,Success)
market.viewOrModify(Success(200))
// res0: Either[Request,Int] = Right(200)
market.review(9)
// res1: Request = Success(200)
We can later on create a new instance of an APrism
or a Prism
from the Market instance
import proptics.Prsim
// import proptics.Prsim
import proptics.APrsim
// import proptics.APrsim
val aPrismFromMarket: APrism[Json, Int] = APrism[Request, Int](market.viewOrModify)(market.review)
// aPrismFromMarket: proptics.APrism[Request,Int] = proptics.APrism_$$anon$14@72c21447
val prismFormMarket: Prism[Request, Int] = Prism[Request, Int](market.viewOrModify)(market.review)
// prismFormMarket: proptics.Prism[Request,Int] = proptics.Prism_$$anon$13@afe505b
Laws
An APrism
must satisfy all APrismLaws. These laws reside in the <a href="../../api/proptics/law/>proptics.law package.
import cats.Eq
// import cats.Eq
import cats.syntax.eq._
// import cats.syntax.eq._
// triple equals operator (===)
implicit val eqRequest: Eq[Request] = Eq.instance {
case (Pending, Pending) => true
case (Success(v1), Success(v2)) => v1 === v2
case (Error(s1), Error(s2)) => s1 === s2
case _ => false
}
// eqRequest: cats.Eq[Request] = cats.kernel.Eq$$anon$5@5a9a178c
Some
Review and then preview a value, will get you back the value wrapped with def previewReview[S, A: Eq](prism: APrism[S, A], a: A): Boolean =
prism.preview(prism.review(a)) match {
case Some(value) => value === a
case _ => false
}
previewReview(successRequest, 200)
// res0: Boolean = true
Preview and then review the structure, will get you back the same structure
If you can extract a value
A
using a PrismP
from a valueS
, then the valueS
is completely described byP
andA
def viewOrModifyReview[S: Eq, A](prism: APrism[S, A], s: S): Boolean =
prism.viewOrModify(s).fold(identity, prism.review) === s
viewOrModifyReview(successRequest, Success(200))
// res1: Boolean = true
Setting twice is the same as setting once
def setSet[S: Eq, A](prism: APrism[S, A], s: S, a: A): Boolean =
prism.set(a)(prism.set(a)(s)) === prism.set(a)(s)
setSet(successRequest, Success(200), 204)
// res2: Boolean = true