All Downloads are FREE. Search and download functionalities are using the official Maven repository.

monocle.function.Plated.scala Maven / Gradle / Ivy

The newest version!
package monocle.function

import monocle.{Setter, Traversal}

import scala.annotation.implicitNotFound
import cats.{Applicative, Monad, Traverse}
import cats.data.State
import cats.instances.int._
import cats.instances.lazyList._
import cats.syntax.flatMap._

/** [[Plated]] is a type-class for types which can extract their immediate
  * self-similar children.
  *
  * @tparam A the parent and child type of a [[Plated]]
  */
@implicitNotFound(
  "Could not find an instance of Plated[${A}], please check Monocle instance location policy to " + "find out which import is necessary"
)
abstract class Plated[A] extends Serializable { self =>
  def plate: Traversal[A, A]
}

trait CommonPlatedFunctions {

  /** [[Traversal]] of immediate self-similar children */
  def plate[A](implicit P: Plated[A]): Traversal[A, A] = P.plate
}

trait PlatedFunctions extends CommonPlatedFunctions {

  /** get the immediate self-similar children of a target */
  def children[A: Plated](a: A): List[A] = plate[A].getAll(a)

  /** rewrite a target by applying a rule as often as possible until it reaches
    * a fixpoint (this is an infinite loop if there is no fixpoint)
    */
  def rewrite[A: Plated](f: A => Option[A])(a: A): A =
    rewriteOf(plate[A].asSetter)(f)(a)

  /** rewrite a target by applying a rule within a [[Setter]], as often as
    * possible until it reaches a fixpoint (this is an infinite loop if there is
    * no fixpoint)
    */
  def rewriteOf[A](l: Setter[A, A])(f: A => Option[A])(a: A): A = {
    def go(b: A): A = {
      val c = transformOf(l)(go)(b)
      f(c).fold(c)(go)
    }
    go(a)
  }

  /** transform every element */
  def transform[A: Plated](f: A => A)(a: A): A =
    transformOf(plate[A].asSetter)(f)(a)

  /** transform every element by applying a [[Setter]] */
  def transformOf[A](l: Setter[A, A])(f: A => A)(a: A): A =
    l.modify(b => transformOf(l)(f)(f(b)))(a)

  /** transforming counting changes */
  def transformCounting[A: Plated](f: A => Option[A])(a: A): (Int, A) =
    transformM[A, State[Int, *]] { b =>
      f(b)
        .map(c => State((i: Int) => (i + 1, c)))
        .getOrElse(State.pure(b))
    }(a).runEmpty.value

  /** transforming every element using monadic transformation */
  def transformM[A: Plated, M[_]: Monad](f: A => M[A])(a: A): M[A] = {
    val l = plate[A]
    def go(c: A): M[A] =
      l.modifyF[M](b => f(b).flatMap(go))(c)
    go(a)
  }

  /** get all transitive self-similar elements of a target, including itself */
  def universe[A: Plated](a: A): LazyList[A] = {
    val fold                  = plate[A].asFold
    def go(b: A): LazyList[A] = b #:: fold.foldMap[LazyList[A]](go)(b)
    go(a)
  }
}

object Plated extends PlatedFunctions {
  def apply[A](traversal: Traversal[A, A]): Plated[A] =
    new Plated[A] {
      override val plate: Traversal[A, A] = traversal
    }

  /** *********************************************************************************************
    */
  /** Std instances */
  /** *********************************************************************************************
    */
  implicit def listPlated[A]: Plated[List[A]] =
    Plated(
      new Traversal[List[A], List[A]] {
        def modifyF[F[_]: Applicative](f: List[A] => F[List[A]])(s: List[A]): F[List[A]] =
          s match {
            case x :: xs => Applicative[F].map(f(xs))(x :: _)
            case Nil     => Applicative[F].pure(Nil)
          }
      }
    )

  implicit def lazyListPlated[A]: Plated[LazyList[A]] =
    Plated(
      new Traversal[LazyList[A], LazyList[A]] {
        def modifyF[F[_]: Applicative](f: LazyList[A] => F[LazyList[A]])(s: LazyList[A]): F[LazyList[A]] =
          s match {
            case x #:: xs   => Applicative[F].map(f(xs))(x #:: _)
            case LazyList() => Applicative[F].pure(LazyList.empty)
          }
      }
    )

  implicit val stringPlated: Plated[String] = Plated(
    new Traversal[String, String] {
      def modifyF[F[_]: Applicative](f: String => F[String])(s: String): F[String] =
        s.headOption match {
          case Some(h) => Applicative[F].map(f(s.tail))(h.toString ++ _)
          case None    => Applicative[F].pure("")
        }
    }
  )

  implicit def vectorPlated[A]: Plated[Vector[A]] =
    Plated(
      new Traversal[Vector[A], Vector[A]] {
        def modifyF[F[_]: Applicative](f: Vector[A] => F[Vector[A]])(s: Vector[A]): F[Vector[A]] =
          s match {
            case h +: t => Applicative[F].map(f(t))(h +: _)
            case _      => Applicative[F].pure(Vector.empty)
          }
      }
    )

  /** *********************************************************************************************
    */
  /** Cats instances */
  /** *********************************************************************************************
    */
  import cats.Now
  import cats.data.Chain
  import cats.free.{Cofree, Free}

  implicit def chainPlated[A]: Plated[Chain[A]] =
    new Plated[Chain[A]] {
      val plate: Traversal[Chain[A], Chain[A]] =
        new Traversal[Chain[A], Chain[A]] {
          def modifyF[F[_]: Applicative](f: Chain[A] => F[Chain[A]])(s: Chain[A]): F[Chain[A]] =
            s.uncons match {
              case Some((x, xs)) => Applicative[F].map(f(xs))(_.prepend(x))
              case None          => Applicative[F].pure(Chain.empty)
            }
        }
    }

  implicit def cofreePlated[S[_]: Traverse, A]: Plated[Cofree[S, A]] =
    Plated(
      new Traversal[Cofree[S, A], Cofree[S, A]] {
        def modifyF[F[_]: Applicative](f: Cofree[S, A] => F[Cofree[S, A]])(s: Cofree[S, A]): F[Cofree[S, A]] =
          Applicative[F].map(Traverse[S].traverse(s.tail.value)(f))(t => Cofree(s.head, Now(t)))
      }
    )

  implicit def freePlated[S[_]: Traverse, A]: Plated[Free[S, A]] =
    Plated(
      new Traversal[Free[S, A], Free[S, A]] {
        def modifyF[F[_]: Applicative](f: Free[S, A] => F[Free[S, A]])(s: Free[S, A]): F[Free[S, A]] =
          s.resume.fold(
            as => Applicative[F].map(Traverse[S].traverse(as)(f))(Free.roll),
            x => Applicative[F].pure(Free.pure(x))
          )
      }
    )
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy