monocle.Iso.scala Maven / Gradle / Ivy
The newest version!
package monocle
import cats.{Applicative, Eq, Functor, Monoid}
import cats.arrow.Category
import cats.evidence.{<~<, Is}
import cats.syntax.either._
import monocle.function.{At, Each, FilterIndex, Index}
/** [[Iso]] is a type alias for [[PIso]] where `S` = `A` and `T` = `B`:
* {{{
* type Iso[S, A] = PIso[S, S, A, A]
* }}}
*
* An [[Iso]] defines an isomorphism between a type S and A:
*
* get
* -------------------->
* S A
* <--------------------
* reverseGet
*
*
* A [[PIso]] allows to lift a function `f: A => B` to `S => T` and a function `g: T => S` to `B => A`
*
* g
* S T S <-------- T
* | ↑ | ↑
* | | | |
* get | | reverseGet reverse.reverseGet | | reverse.get
* | | | |
* ↓ f | ↓ |
* A --------> B A B
*
*
* A [[PIso]] is also a valid [[Getter]], [[Fold]], [[PLens]], [[PPrism]], [[POptional]], [[PTraversal]] and [[PSetter]]
*
* @see [[monocle.law.IsoLaws]]
*
* @tparam S the source of a [[PIso]]
* @tparam T the modified source of a [[PIso]]
* @tparam A the target of a [[PIso]]
* @tparam B the modified target of a [[PIso]]
*/
abstract class PIso[S, T, A, B] extends Serializable { self =>
/** get the target of a [[PIso]] */
def get(s: S): A
/** get the modified source of a [[PIso]] */
def reverseGet(b: B): T
/** reverse a [[PIso]]: the source becomes the target and the target becomes the source */
def reverse: PIso[B, A, T, S]
/** lift a [[PIso]] to a Functor level */
def mapping[F[_]: Functor]: PIso[F[S], F[T], F[A], F[B]] =
PIso[F[S], F[T], F[A], F[B]](fs => Functor[F].map(fs)(self.get))(fb => Functor[F].map(fb)(self.reverseGet))
/** find if the target satisfies the predicate */
final def find(p: A => Boolean): S => Option[A] =
s => Some(get(s)).filter(p)
/** check if the target satisfies the predicate */
final def exist(p: A => Boolean): S => Boolean =
p compose get
/** modify polymorphically the target of a [[PIso]] with a Functor function */
final def modifyF[F[_]: Functor](f: A => F[B])(s: S): F[T] =
Functor[F].map(f(get(s)))(reverseGet)
/** modify polymorphically the target of a [[PIso]] with a function */
final def modify(f: A => B): S => T =
s => reverseGet(f(get(s)))
@deprecated("use replace instead", since = "3.0.0-M1")
final def set(b: B): S => T = replace(b)
/** replace polymorphically the target of a [[PIso]] with a value */
final def replace(b: B): S => T =
_ => reverseGet(b)
/** pair two disjoint [[PIso]] */
final def split[S1, T1, A1, B1](other: PIso[S1, T1, A1, B1]): PIso[(S, S1), (T, T1), (A, A1), (B, B1)] =
PIso[(S, S1), (T, T1), (A, A1), (B, B1)] { case (s, s1) =>
(get(s), other.get(s1))
} { case (b, b1) =>
(reverseGet(b), other.reverseGet(b1))
}
final def first[C]: PIso[(S, C), (T, C), (A, C), (B, C)] =
PIso[(S, C), (T, C), (A, C), (B, C)] { case (s, c) =>
(get(s), c)
} { case (b, c) =>
(reverseGet(b), c)
}
final def second[C]: PIso[(C, S), (C, T), (C, A), (C, B)] =
PIso[(C, S), (C, T), (C, A), (C, B)] { case (c, s) =>
(c, get(s))
} { case (c, b) =>
(c, reverseGet(b))
}
final def left[C]: PIso[Either[S, C], Either[T, C], Either[A, C], Either[B, C]] =
PIso[Either[S, C], Either[T, C], Either[A, C], Either[B, C]](_.leftMap(get))(_.leftMap(reverseGet))
final def right[C]: PIso[Either[C, S], Either[C, T], Either[C, A], Either[C, B]] =
PIso[Either[C, S], Either[C, T], Either[C, A], Either[C, B]](_.map(get))(_.map(reverseGet))
def some[A1, B1](implicit ev1: A =:= Option[A1], ev2: B =:= Option[B1]): PPrism[S, T, A1, B1] =
adapt[Option[A1], Option[B1]] composePrism (std.option.pSome)
private[monocle] def adapt[A1, B1](implicit evA: A =:= A1, evB: B =:= B1): PIso[S, T, A1, B1] =
evB.substituteCo[PIso[S, T, A1, *]](evA.substituteCo[PIso[S, T, *, B]](this))
/** compose a [[PIso]] with a [[Fold]] */
final def andThen[C](other: Fold[A, C]): Fold[S, C] =
asFold.andThen(other)
/** compose a [[PIso]] with a [[Getter]] */
final def andThen[C](other: Getter[A, C]): Getter[S, C] =
asGetter.andThen(other)
/** compose a [[PIso]] with a [[PSetter]] */
final def andThen[C, D](other: PSetter[A, B, C, D]): PSetter[S, T, C, D] =
asSetter.andThen(other)
/** compose a [[PIso]] with a [[PTraversal]] */
final def andThen[C, D](other: PTraversal[A, B, C, D]): PTraversal[S, T, C, D] =
asTraversal.andThen(other)
/** compose a [[PIso]] with a [[POptional]] */
final def andThen[C, D](other: POptional[A, B, C, D]): POptional[S, T, C, D] =
asOptional.andThen(other)
/** compose a [[PIso]] with a [[PPrism]] */
final def andThen[C, D](other: PPrism[A, B, C, D]): PPrism[S, T, C, D] =
asPrism.andThen(other)
/** compose a [[PIso]] with a [[PLens]] */
final def andThen[C, D](other: PLens[A, B, C, D]): PLens[S, T, C, D] =
asLens.andThen(other)
/** compose a [[PIso]] with another [[PIso]] */
final def andThen[C, D](other: PIso[A, B, C, D]): PIso[S, T, C, D] =
new PIso[S, T, C, D] { composeSelf =>
def get(s: S): C =
other.get(self.get(s))
def reverseGet(d: D): T =
self.reverseGet(other.reverseGet(d))
def reverse: PIso[D, C, T, S] =
new PIso[D, C, T, S] {
def get(d: D): T =
self.reverseGet(other.reverseGet(d))
def reverseGet(s: S): C =
other.get(self.get(s))
def reverse: PIso[S, T, C, D] =
composeSelf
}
}
/** compose a [[PIso]] with a [[Fold]] */
@deprecated("use andThen", since = "3.0.0-M1")
final def composeFold[C](other: Fold[A, C]): Fold[S, C] =
andThen(other)
/** Compose with a function lifted into a Getter */
def to[C](f: A => C): Getter[S, C] = composeGetter(Getter(f))
/** compose a [[PIso]] with a [[Getter]] */
@deprecated("use andThen", since = "3.0.0-M1")
final def composeGetter[C](other: Getter[A, C]): Getter[S, C] =
andThen(other)
/** compose a [[PIso]] with a [[PSetter]] */
@deprecated("use andThen", since = "3.0.0-M1")
final def composeSetter[C, D](other: PSetter[A, B, C, D]): PSetter[S, T, C, D] =
andThen(other)
/** compose a [[PIso]] with a [[PTraversal]] */
@deprecated("use andThen", since = "3.0.0-M1")
final def composeTraversal[C, D](other: PTraversal[A, B, C, D]): PTraversal[S, T, C, D] =
andThen(other)
/** compose a [[PIso]] with a [[POptional]] */
@deprecated("use andThen", since = "3.0.0-M1")
final def composeOptional[C, D](other: POptional[A, B, C, D]): POptional[S, T, C, D] =
andThen(other)
/** compose a [[PIso]] with a [[PPrism]] */
@deprecated("use andThen", since = "3.0.0-M1")
final def composePrism[C, D](other: PPrism[A, B, C, D]): PPrism[S, T, C, D] =
andThen(other)
/** compose a [[PIso]] with a [[PLens]] */
@deprecated("use andThen", since = "3.0.0-M1")
final def composeLens[C, D](other: PLens[A, B, C, D]): PLens[S, T, C, D] =
andThen(other)
/** compose a [[PIso]] with a [[PIso]] */
@deprecated("use andThen", since = "3.0.0-M1")
final def composeIso[C, D](other: PIso[A, B, C, D]): PIso[S, T, C, D] =
andThen(other)
/** *****************************************
*/
/** Experimental aliases of compose methods */
/** *****************************************
*/
/** alias to composeTraversal */
@deprecated("use andThen", since = "3.0.0-M1")
final def ^|->>[C, D](other: PTraversal[A, B, C, D]): PTraversal[S, T, C, D] =
andThen(other)
/** alias to composeOptional */
@deprecated("use andThen", since = "3.0.0-M1")
final def ^|-?[C, D](other: POptional[A, B, C, D]): POptional[S, T, C, D] =
andThen(other)
/** alias to composePrism */
@deprecated("use andThen", since = "3.0.0-M1")
final def ^<-?[C, D](other: PPrism[A, B, C, D]): PPrism[S, T, C, D] =
andThen(other)
/** alias to composeLens */
@deprecated("use andThen", since = "3.0.0-M1")
final def ^|->[C, D](other: PLens[A, B, C, D]): PLens[S, T, C, D] =
andThen(other)
/** alias to composeIso */
@deprecated("use andThen", since = "3.0.0-M1")
final def ^<->[C, D](other: PIso[A, B, C, D]): PIso[S, T, C, D] =
andThen(other)
/** *************************************************************
*/
/** Transformation methods to view a [[PIso]] as another Optics */
/** *************************************************************
*/
/** view a [[PIso]] as a [[Fold]] */
final def asFold: Fold[S, A] =
new Fold[S, A] {
def foldMap[M: Monoid](f: A => M)(s: S): M =
f(get(s))
}
/** view a [[PIso]] as a [[Getter]] */
final def asGetter: Getter[S, A] =
(s: S) => self.get(s)
/** view a [[PIso]] as a [[Setter]] */
final def asSetter: PSetter[S, T, A, B] =
new PSetter[S, T, A, B] {
def modify(f: A => B): S => T =
self.modify(f)
def replace(b: B): S => T =
self.replace(b)
}
/** view a [[PIso]] as a [[PTraversal]] */
final def asTraversal: PTraversal[S, T, A, B] =
new PTraversal[S, T, A, B] {
def modifyF[F[_]: Applicative](f: A => F[B])(s: S): F[T] =
self.modifyF(f)(s)
}
/** view a [[PIso]] as a [[POptional]] */
final def asOptional: POptional[S, T, A, B] =
new POptional[S, T, A, B] {
def getOrModify(s: S): Either[T, A] =
Either.right(get(s))
def replace(b: B): S => T =
self.replace(b)
def getOption(s: S): Option[A] =
Some(self.get(s))
def modify(f: A => B): S => T =
self.modify(f)
def modifyF[F[_]: Applicative](f: A => F[B])(s: S): F[T] =
self.modifyF(f)(s)
}
/** view a [[PIso]] as a [[PPrism]] */
final def asPrism: PPrism[S, T, A, B] =
new PPrism[S, T, A, B] {
def getOrModify(s: S): Either[T, A] =
Either.right(get(s))
def reverseGet(b: B): T =
self.reverseGet(b)
def getOption(s: S): Option[A] =
Some(self.get(s))
}
/** view a [[PIso]] as a [[PLens]] */
final def asLens: PLens[S, T, A, B] =
new PLens[S, T, A, B] {
def get(s: S): A =
self.get(s)
def replace(b: B): S => T =
self.replace(b)
def modify(f: A => B): S => T =
self.modify(f)
def modifyF[F[_]: Functor](f: A => F[B])(s: S): F[T] =
self.modifyF(f)(s)
}
/** **********************************************************************
*/
/** Apply methods to treat a [[PIso]] as smart constructors for type T */
/** **********************************************************************
*/
def apply()(implicit ev: Is[B, Unit]): T =
ev.substitute[PIso[S, T, A, *]](self).reverseGet(())
def apply(b: B): T = reverseGet(b)
def apply[C, D](c: C, d: D)(implicit ev: (C, D) <~< B): T = apply(ev((c, d)))
def apply[C, D, E](c: C, d: D, e: E)(implicit ev: (C, D, E) <~< B): T =
apply(ev((c, d, e)))
def apply[C, D, E, F](c: C, d: D, e: E, f: F)(implicit ev: (C, D, E, F) <~< B): T =
apply(ev((c, d, e, f)))
def apply[C, D, E, F, G](c: C, d: D, e: E, f: F, g: G)(implicit ev: (C, D, E, F, G) <~< B): T =
apply(ev((c, d, e, f, g)))
def apply[C, D, E, F, G, H](c: C, d: D, e: E, f: F, g: G, h: H)(implicit ev: (C, D, E, F, G, H) <~< B): T =
apply(ev((c, d, e, f, g, h)))
def unapply(obj: S): Some[A] = Some(get(obj))
}
object PIso extends IsoInstances {
/** create a [[PIso]] using a pair of functions: one to get the target and one to get the source. */
def apply[S, T, A, B](_get: S => A)(_reverseGet: B => T): PIso[S, T, A, B] =
new PIso[S, T, A, B] { self =>
def get(s: S): A =
_get(s)
def reverseGet(b: B): T =
_reverseGet(b)
def reverse: PIso[B, A, T, S] =
new PIso[B, A, T, S] {
def get(b: B): T =
_reverseGet(b)
def reverseGet(s: S): A =
_get(s)
def reverse: PIso[S, T, A, B] =
self
}
}
/** create a [[PIso]] between any type and itself. id is the zero element of optics composition,
* for all optics o of type O (e.g. Lens, Iso, Prism, ...):
* o composeIso Iso.id == o
* Iso.id composeO o == o (replace composeO by composeLens, composeIso, composePrism, ...)
*/
def id[S, T]: PIso[S, T, S, T] =
new PIso[S, T, S, T] { self =>
def get(s: S): S = s
def reverseGet(t: T): T = t
def reverse: PIso[T, S, T, S] =
new PIso[T, S, T, S] {
def get(t: T): T = t
def reverseGet(s: S): S = s
def reverse: PIso[S, T, S, T] = self
}
}
implicit def isoSyntax[S, A](self: Iso[S, A]): IsoSyntax[S, A] =
new IsoSyntax(self)
}
object Iso {
/** alias for [[PIso]] apply when S = T and A = B */
def apply[S, A](get: S => A)(reverseGet: A => S): Iso[S, A] =
PIso(get)(reverseGet)
/** alias for [[PIso]] id when S = T and A = B */
def id[S]: Iso[S, S] =
PIso.id[S, S]
/** create an [[Iso]] from a function that is its own inverse */
def involuted[A](update: A => A): Iso[A, A] =
Iso(update)(update)
}
sealed abstract class IsoInstances {
implicit val isoCategory: Category[Iso] = new Category[Iso] {
def id[A]: Iso[A, A] =
Iso.id[A]
def compose[A, B, C](f: Iso[B, C], g: Iso[A, B]): Iso[A, C] =
g composeIso f
}
}
/** Extension methods for monomorphic Iso */
final case class IsoSyntax[S, A](private val self: Iso[S, A]) extends AnyVal {
def each[C](implicit evEach: Each[A, C]): Traversal[S, C] =
self composeTraversal evEach.each
/** Select all the elements which satisfies the predicate.
* This combinator can break the fusion property see Optional.filter for more details.
*/
def filter(predicate: A => Boolean): Optional[S, A] =
self.andThen(Optional.filter(predicate))
def filterIndex[I, A1](predicate: I => Boolean)(implicit ev: FilterIndex[A, I, A1]): Traversal[S, A1] =
self.andThen(ev.filterIndex(predicate))
def withDefault[A1: Eq](defaultValue: A1)(implicit evOpt: A =:= Option[A1]): Iso[S, A1] =
self.adapt[Option[A1], Option[A1]] composeIso (std.option.withDefault(defaultValue))
def at[I, A1](i: I)(implicit evAt: At[A, i.type, A1]): Lens[S, A1] =
self.andThen(evAt.at(i))
def index[I, A1](i: I)(implicit evIndex: Index[A, I, A1]): Optional[S, A1] =
self composeOptional evIndex.index(i)
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy