Iso
An Iso
enables you to transform back and forth between two types without losing information.
Iso
is useful when you need to convert between types, a simple example would be, transform a String
into a List[Char]
and from List[Char]
to String
.
Constructing a monomorphic Iso
Iso[S, A]
is constructed using the Iso[S, A]#apply function. For a given Iso[S, A]
it takes two conversion functions as arguments,
view: S => A
which produces an A
given an S
, and review: A => S
which produces an S
given an A
.
object Iso {
def apply[S, A](view: S => A)(review: A => S): Iso[S, A]
}
import proptics.Iso
val isoStringToList = Iso[String, List[Char]](_.toList)(_.mkString)
val propticsStr = "proptics"
val uppercaseChars = propticsStr.toList.map(c => (c - 32).toChar)
isoStringToList.view(propticsStr)
// val res0: List[Char] = List(p, r, o, p, t, i, c, s)
isoStringToList.set(uppercaseChars)(propticsStr)
// val res1: String = PROPTICS
Constructing a polymorphic Iso
Iso_[S, T, A, B]
is constructed using the Iso_[S, T, A, B]#apply function.
For a given Iso_[S, T, A, B]
it takes two conversion functions as arguments, view: S => A
which produces an A
given an S
,
and review: B => T
which produces a T
given an B
.
object Iso_ {
def apply[S, T, A, B](view: S => A)(review: B => T): Iso_[S, T, A, B]
}
Methods
view
/** view the focus of an Iso */
def view(s: S): A
isoStringToList.view("Proptics")
// val res0: List[Char] = List(P, r, o, p, t, i, c, s)
review
/** view the source of an Iso (construct an S from an A) */
def review(a: A): S
isoStringToList.review()
// val res1: String = Proptics
set
/** set the focus of an Iso */
def set(a: A): S => S
val source: String = "Profunctor"
isoStringToList.set(List('P', 'r', 'o', 'p', 't', 'i', 'c', 's'))(source)
// val res2: String = Proptics
over
/** modify the focus of an Iso using a function */
def over(f: A => A): S => S
isoStringToList.over(_.map(_.toUpper))("Proptics")
// val res3: String = PROPTICS
traverse
/** modify the focus of an Iso using a Functor */
def traverse[F[_]](s: S)(f: A => F[A])(implicit arg0: Applicative[F]): F[S]
def isProSuffix(chars: List[Char]): Option[List[Char]] =
Option.when(chars.startsWith("Prop"))(chars)
isoStringToList.traverse("Proptics")(isProSuffix)
// val res4: Option[String] = Some(Proptics)
isoStringToList.traverse("Profunctor")(isProSuffix)
// val res5: Option[String] = None
overF
/** synonym for traverse, flipped */
def overF[F[_]](f: A => F[B])(s: S)(implicit arg0: Applicative[F]): F[T]
def isProSuffix(chars: List[Char]): Option[List[Char]] =
Option.when(chars.startsWith("Prop"))(chars)
val partialLens = isoStringToList.overF(isProSuffix) _
partialLens("Proptics")
// val res6: : Option[String] = Some(Proptics)
partialLens("Profunctor")
// val res7: Option[User] = None
exists
/** test whether a predicate holds for the focus of an Iso */
def exists(f: A => Boolean): S => Boolean
isoStringToList.exists(_.length === 8)("Proptics")
// val res8: Boolean = true
notExists
/** test whether a predicate does not hold for the focus of an Iso */
def notExists(f: A => Boolean): S => Boolean
isoStringToList.notExists(_.startsWith("Prop"))("Proptics")
// val res9: Boolean = false
contains
/** test whether the focus of an Iso contains a given value */
def contains(a: A)(s: S)(implicit ev: Eq[A]): Boolean
val chars = List('P', 'r', 'o', 'p', 't', 'i', 'c', 's')
isoStringToList.contains(chars)("Proptics")
// val res10: Boolean = true
notContains
/** test whether the focus of an Iso does not contain a given value */
def notContains(a: A)(s: S)(implicit ev: Eq[A]): Boolean
val chars = List('P', 'r', 'o', 'p', 't', 'i', 'c', 's')
isoStringToList.notContains(chars)("Profunctor")
// val res11: Boolean = true
find
/** find the focus of an Iso that satisfies a predicate, if there is any */
def find(f: A => Boolean): S => Option[A]
isoStringToList.find(_.contains('P'))("Proptics")
// val res12: Option[List[Char]] = Some(List(P, r, o, p, t, i, c, s))
cotraverse
/** modify an effectual focus of an Iso into the modified focus */
def cotraverse[F[_]](fs: F[S])(f: F[A] => A)(implicit arg0: Comonad[F]): S
import cats.Id
val functorChars = List('f', 'u', 'n', 'c', 't', 'o', 'r')
isoStringToList.cotraverse(Id("Proptics"))(_.take(3) ++ functorChars)
// val res13: String = Profunctor
zipWithF
/** synonym for [[cotraverse]], flipped */
def zipWithF[F[_]](fs: F[S])(f: F[A] => A)(implicit arg0: Comonad[F]): S
import cats.Id
val functorChars = List('f', 'u', 'n', 'c', 't', 'o', 'r')
isoStringToList.zipWithF[Id](_.take(3) ++ functorChars)("Proptics")
// val res14: String = Profunctor
zipWith
/** zip two sources of an Iso together provided a binary operation */
def zipWith(s1: S, s2: S)(f: (A, A) => A): S
isoStringToList.zipWith("Pro", "ptics")(_ ++ _)
// val res15: String = Proptics
use
/** view the focus of an Iso in the state of a monad */
def use(implicit ev: State[S, A]): State[S, A]
implicit val state: State[String, List[Char]] = State.set("Proptics").inspect(_.toList)
isoStringToList.use.run("Profunctor").value
// val res16: (String, List[Char]) = (Proptics,List(P, r, o, p, t, i, c, s))
Iso internal encoding
Polymorphic Iso
Iso_[S, T, A, B]
Iso_[S, T, A, B]
is a function P[A, B] => P[S, T]
that takes a Profunctor of P[_, _].
/**
* @tparam S the source of an Iso_
* @tparam T the modified source of an Iso_
* @tparam A the focus of an Iso_
* @tparam B the modified focus of a Iso_
*/
abstract class Iso_[S, T, A, B] {
def apply[P[_, _]](pab: P[A, B])(implicit ev: Profunctor[P]): P[S, T]
}
Iso_[S, T, A, B]
changes its focus from A
to B
, resulting in a change of type to the full structure from
S
to T
, the same as changing one element of a tuple (focus), would give us a new type (structure) of tuple.
An Iso
that changes its focus/structure, is called Polymorphic Iso
.
Monomorphic Iso
Iso[S, A]
Iso[S, A]
is a type alias for Iso_[S, S, A, A]
, which has the same type of focus A
, thus preserving the same type of structure S
.
type Iso[S, A] = Iso_[S, S, A, A]
Iso[S, A]
means that S
and A
are isomorphic – the two types represent the same information.
An Iso
that does not change its focus/structure, is called Monomorphic Iso
.
Laws
An Iso
must satisfy all IsoLaws. These laws reside in the proptics.law package.
All laws constructed from the reversibility law, which says that we can completely reverse the transformation.
import proptics.Iso
import cats.Eq
import cats.instances.string._
import cats.syntax.eq._
val isoStringToList = Iso[String, List[Char]](_.toList)(_.mkString)
Source reversibility
def sourceReversibility[S: Eq, A](iso: Iso[S, A], s: S): Boolean =
iso.review(iso.view(s)) === s
sourceReversibility(isoStringToList, "Proptics")
// val res0: Boolean = true
Focus reversibility
def focusReversibility[S, A: Eq](iso: Iso[S, A], a: A): Boolean =
iso.view(iso.review(a)) === a
focusReversibility(isoStringToList, "Proptics".toList)
// val res1: Boolean = true