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

higherkindness.droste.scheme.scala Maven / Gradle / Ivy

The newest version!
package higherkindness.droste

import cats.Functor
import cats.Monad
import cats.Traverse
import cats.syntax.applicative._
import cats.syntax.functor._
import cats.syntax.flatMap._
import cats.syntax.traverse._
import implicits.composedFunctor._

/** @groupname refolds
  *   Refolds
  * @groupname folds
  *   Folds
  * @groupname unfolds
  *   Unfolds
  * @groupname exotic
  *   Exotic
  */
object scheme
    extends SchemeHyloPorcelain
    with SchemeConvenientPorcelain
    with SchemeGeneralizedPorcelain {

  /** A petting zoo for wild and exotic animals we keep separate from the
    * regulars in [[scheme]]. For their safety and yours.
    *
    * @group exotic
    *
    * @groupname refolds
    *   Rambunctious Refolds
    * @groupname folds
    *   Fantastic Folds
    * @groupname unfolds
    *   Unusual Unfolds
    */
  object zoo extends Zoo
}

private[droste] sealed trait SchemeConvenientPorcelain {

  // these _could_ go in the zoo, but they are very common for people
  // learning recursion schemes, so it's nice to have them here

  def ana[F[_]: Functor, A, R](
      coalgebra: Coalgebra[F, A]
  )(implicit embed: Embed[F, R]): A => R =
    kernel.hylo(embed.algebra.run, coalgebra.run)

  def anaM[M[_]: Monad, F[_]: Traverse, A, R](
      coalgebraM: CoalgebraM[M, F, A]
  )(implicit embed: Embed[F, R]): A => M[R] =
    kernel.hyloM(embed.algebra.lift[M].run, coalgebraM.run)

  def cata[F[_]: Functor, R, B](
      algebra: Algebra[F, B]
  )(implicit project: Project[F, R]): R => B =
    kernel.hylo(algebra.run, project.coalgebra.run)

  def cataM[M[_]: Monad, F[_]: Traverse, R, B](
      algebraM: AlgebraM[M, F, B]
  )(implicit project: Project[F, R]): R => M[B] =
    kernel.hyloM(algebraM.run, project.coalgebra.lift[M].run)

  /** Convenience to specify the base constructor "shape" (such as `Fix` or
    * `Cofree[*[_], Int]`) for recursion.
    *
    * This helps to guide Scala's type inference so all of the type parameters
    * for the various recursion scheme methods don't have to be provided.
    */
  def apply[PatR[_[_]]](implicit
      ev: Basis.Solve[PatR]
  ): SchemePartialBasis[PatR, ev.PatF] =
    new SchemePartialBasis[PatR, ev.PatF]

  final class SchemePartialBasis[PatR[_[_]], PatF[_[_], _]] private[droste] () {

    type EmbedP[F[_]]    = Embed[PatF[F, *], PatR[F]]
    type ProjectP[F[_]]  = Project[PatF[F, *], PatR[F]]
    type FunctorP[F[_]]  = Functor[PatF[F, *]]
    type TraverseP[F[_]] = Traverse[PatF[F, *]]

    def ana[F[_], A](
        coalgebra: Coalgebra[PatF[F, *], A]
    )(implicit embed: EmbedP[F], ev: FunctorP[F]): A => PatR[F] =
      scheme.ana[PatF[F, *], A, PatR[F]](coalgebra)

    def anaM[M[_]: Monad, F[_], A](
        coalgebraM: CoalgebraM[M, PatF[F, *], A]
    )(implicit embed: EmbedP[F], ev: TraverseP[F]): A => M[PatR[F]] =
      scheme.anaM[M, PatF[F, *], A, PatR[F]](coalgebraM)

    def gana[F[_], A, S](
        scattered: GCoalgebra.Scattered[PatF[F, *], A, S]
    )(implicit embed: EmbedP[F], ev: FunctorP[F]): A => PatR[F] =
      scheme.gana[PatF[F, *], A, S, PatR[F]](scattered.coalgebra)(
        scattered.scatter
      )

    def ganaM[M[_]: Monad, F[_], A, S](
        scattered: GCoalgebraM.Scattered[M, PatF[F, *], A, S]
    )(implicit embed: EmbedP[F], ev: TraverseP[F]): A => M[PatR[F]] =
      scheme.ganaM[M, PatF[F, *], A, S, PatR[F]](scattered)

    def cata[F[_], B](
        algebra: Algebra[PatF[F, *], B]
    )(implicit project: ProjectP[F], ev: FunctorP[F]): PatR[F] => B =
      scheme.cata[PatF[F, *], PatR[F], B](algebra)

    def cataM[M[_]: Monad, F[_], B](
        algebraM: AlgebraM[M, PatF[F, *], B]
    )(implicit project: ProjectP[F], ev: TraverseP[F]): PatR[F] => M[B] =
      scheme.cataM[M, PatF[F, *], PatR[F], B](algebraM)

    def gcata[F[_], S, B](
        gathered: GAlgebra.Gathered[PatF[F, *], S, B]
    )(implicit project: ProjectP[F], ev: FunctorP[F]): PatR[F] => B =
      scheme.gcata[PatF[F, *], PatR[F], S, B](gathered.algebra)(gathered.gather)

    def gcataM[M[_]: Monad, F[_], S, B](
        gathered: GAlgebraM.Gathered[M, PatF[F, *], S, B]
    )(implicit project: ProjectP[F], ev: TraverseP[F]): PatR[F] => M[B] =
      scheme.gcataM[M, PatF[F, *], PatR[F], S, B](gathered)

  }

}

private[droste] sealed trait SchemeGeneralizedPorcelain
    extends SchemeGeneralizedPlumbing {

  def ghylo[F[_]: Functor, A, SA, SB, B](
      gathered: GAlgebra.Gathered[F, SB, B],
      scattered: GCoalgebra.Scattered[F, A, SA]
  ): A => B =
    ghylo(gathered.algebra, scattered.coalgebra)(
      gathered.gather,
      scattered.scatter
    )

  def ghyloM[M[_]: Monad, F[_]: Traverse, A, SA, SB, B](
      gathered: GAlgebraM.Gathered[M, F, SB, B],
      scattered: GCoalgebraM.Scattered[M, F, A, SA]
  ): A => M[B] =
    ghyloM(gathered.algebra, scattered.coalgebra)(
      gathered.gather,
      scattered.scatter
    )

  def gcata[F[_]: Functor, R, S, B](
      gathered: GAlgebra.Gathered[F, S, B]
  )(implicit project: Project[F, R]): R => B =
    gcata(gathered.algebra)(gathered.gather)

  def gcataM[M[_]: Monad, F[_]: Traverse, R, S, B](
      gathered: GAlgebraM.Gathered[M, F, S, B]
  )(implicit project: Project[F, R]): R => M[B] =
    gcataM(gathered.algebra)(gathered.gather)

  def gana[F[_]: Functor, A, S, R](
      scattered: GCoalgebra.Scattered[F, A, S]
  )(implicit embed: Embed[F, R]): A => R =
    gana(scattered.coalgebra)(scattered.scatter)

  def ganaM[M[_]: Monad, F[_]: Traverse, A, S, R](
      scattered: GCoalgebraM.Scattered[M, F, A, S]
  )(implicit embed: Embed[F, R]): A => M[R] =
    ganaM(scattered.coalgebra)(scattered.scatter)

}

private[droste] sealed trait SchemeGeneralizedPlumbing {

  def ghylo[F[_]: Functor, A, SA, SB, B](
      algebra: GAlgebra[F, SB, B],
      coalgebra: GCoalgebra[F, A, SA]
  )(
      gather: Gather[F, SB, B],
      scatter: Scatter[F, A, SA]
  ): A => B =
    a =>
      algebra(
        coalgebra(a).map(
          kernel.hylo[F, SA, SB](
            fb => gather(algebra(fb), fb),
            sa => scatter(sa).fold(coalgebra.run, identity)
          )
        )
      )

  def ghyloM[M[_]: Monad, F[_]: Traverse, A, SA, SB, B](
      algebra: GAlgebraM[M, F, SB, B],
      coalgebra: GCoalgebraM[M, F, A, SA]
  )(
      gather: Gather[F, SB, B],
      scatter: Scatter[F, A, SA]
  ): A => M[B] =
    a =>
      coalgebra(a).flatMap(fsa =>
        fsa
          .traverse(
            kernel.hyloM[M, F, SA, SB](
              fb => algebra(fb).map(gather(_, fb)),
              sa => scatter(sa).fold(coalgebra.run, _.pure[M])
            )
          )
          .flatMap(algebra.run)
      )

  def gcata[F[_]: Functor, R, S, B](galgebra: GAlgebra[F, S, B])(
      gather: Gather[F, S, B]
  )(implicit project: Project[F, R]): R => B =
    r =>
      galgebra(
        project
          .coalgebra(r)
          .map(
            kernel.hylo[F, R, S](
              fb => gather(galgebra(fb), fb),
              project.coalgebra.run
            )
          )
      )

  def gcataM[M[_]: Monad, F[_]: Traverse, R, S, B](
      algebra: GAlgebraM[M, F, S, B]
  )(
      gather: Gather[F, S, B]
  )(implicit project: Project[F, R]): R => M[B] =
    r =>
      project
        .coalgebra(r)
        .traverse(
          kernel
            .hyloM[M, F, R, S](
              fb => algebra(fb).map(gather(_, fb)),
              project.coalgebra.lift[M].run
            )
        )
        .flatMap(algebra.run)

  def gana[F[_]: Functor, A, S, R](coalgebra: GCoalgebra[F, A, S])(
      scatter: Scatter[F, A, S]
  )(implicit embed: Embed[F, R]): A => R =
    a =>
      embed.algebra(
        coalgebra(a).map(
          kernel.hylo[F, S, R](
            embed.algebra.run,
            s => scatter(s).fold(coalgebra.run, identity)
          )
        )
      )

  def ganaM[M[_]: Monad, F[_]: Traverse, A, S, R](
      coalgebra: GCoalgebraM[M, F, A, S]
  )(
      scatter: Scatter[F, A, S]
  )(implicit embed: Embed[F, R]): A => M[R] =
    a =>
      coalgebra(a)
        .flatMap(
          _.traverse(
            kernel.hyloM[M, F, S, R](
              embed.algebra.lift[M].run,
              sa => scatter(sa).fold(coalgebra.run, _.pure[M])
            )
          )
        )
        .map(embed.algebra.run)

}

private[droste] sealed trait SchemeHyloPorcelain {

  /** Build a hylomorphism by recursively unfolding with `coalgebra` and
    * refolding with `algebra`.
    *
    * 
 hylo A ---------------> B
    * | ^ co- | | algebra | | algebra
    * | | v | F[A] ------------> F[B] map hylo 
* * @group refolds */ def hylo[F[_]: Functor, A, B]( algebra: Algebra[F, B], coalgebra: Coalgebra[F, A] ): A => B = kernel.hylo(algebra.run, coalgebra.run) /** Build a monadic hylomorphism * *
 hyloM A ---------------> M[B]
    * | ^ co- | | algebraM | | flatMap f
    * | | v | M[F[A]] ---------> M[F[M[B]]] map hyloM
    *
    * with f:
    *
    * F[M[B]] -----> M[F[B]] ----------> M[B] sequence flatMap algebraM 
* * @group refolds */ def hyloM[M[_]: Monad, F[_]: Traverse, A, B]( algebra: AlgebraM[M, F, B], coalgebra: CoalgebraM[M, F, A] ): A => M[B] = kernel.hyloM(algebra.run, coalgebra.run) }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy