Skip to content

Commit

Permalink
Merge pull request #37 from andyscott/rip-dist-law
Browse files Browse the repository at this point in the history
RIP distributive law. Hello gather/scatter
  • Loading branch information
andyscott authored Jul 19, 2018
2 parents a0ffedf + b0aae7d commit 887c471
Show file tree
Hide file tree
Showing 11 changed files with 280 additions and 149 deletions.
2 changes: 1 addition & 1 deletion athema/src/main/scala/qq/athema/algebras.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ object Evaluate {

object Differentiate {
def differentiate[V: Ring]: Expr.Fixed[V] => Expr.Fixed[V] =
scheme.para(algebra[V])
scheme.gcata(algebra[V])(gather.para)

def algebra[V](implicit V: Ring[V]): RAlgebra[Expr.Fixed[V], Expr[V, ?], Expr.Fixed[V]] = {
case _: Var [_, _] => Const.fix(V.one)
Expand Down
3 changes: 3 additions & 0 deletions modules/core/src/main/scala/qq/droste/data/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ object `package` {
*/
type Cofree[F[_], A] // = (A, F[Cofree[F, A]])

type :<[F[_], A] = Cofree[F, A]
val :< = Cofree

object Cofree {
def apply [F[_], A](head: A, tail: F[Cofree[F, A]]): Cofree[F, A] = apply((head, tail))
def apply [F[_], A](f: (A, F[Cofree[F, A]])): Cofree[F, A] = macro Meta.fastCast
Expand Down
22 changes: 22 additions & 0 deletions modules/core/src/main/scala/qq/droste/gather.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package qq.droste

import cats.Functor
import cats.syntax.functor._

import data._

object gather {

def cata[F[_], A]: Gather[F, A, A] =
(a, fa) => a

def zygo[F[_]: Functor, A, B](algebra: Algebra[F, B]): Gather[F, A, (B, A)] =
(a, fa) => (algebra(fa.map(_._1)), a)

def para[F[_]: Functor, A, B](implicit embed: Embed[F, B]): Gather[F, A, (B, A)] =
zygo(embed.algebra)

def histo[F[_], A]: Gather[F, A, Cofree[F, A]] =
Cofree(_, _)

}
5 changes: 5 additions & 0 deletions modules/core/src/main/scala/qq/droste/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,9 @@ object `package` {
type GAlgebra [W[_], F[_], A ] = F[W[A]] => A
type GCoalgebra [W[_], F[_], A ] = A => F[W[A]]


type Gather [F[_], A, S] = (A, F[S]) => S
type Scatter[F[_], A, S] = S => Either[A, F[S]]


}
19 changes: 19 additions & 0 deletions modules/core/src/main/scala/qq/droste/scatter.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package qq.droste

import cats.Functor
import cats.syntax.functor._

object scatter {

def ana[F[_], A]: Scatter[F, A, A] =
Left(_)

def gapo[F[_]: Functor, A, B](coalgebra: Coalgebra[F, B]): Scatter[F, A, Either[B, A]] = {
case Left(b) => Right(coalgebra(b).map(Left(_)))
case Right(a) => Left(a)
}

def apo[F[_]: Functor, A, B](implicit project: Project[F, B]): Scatter[F, A, Either[B, A]] =
gapo(project.coalgebra)

}
173 changes: 34 additions & 139 deletions modules/core/src/main/scala/qq/droste/scheme.scala
Original file line number Diff line number Diff line change
@@ -1,31 +1,21 @@
package qq.droste

import cats.~>
import cats.Comonad
import cats.Functor
import cats.Monad
import cats.Traverse

import cats.syntax.applicative._
import cats.syntax.coflatMap._
import cats.syntax.comonad._
import cats.syntax.functor._
import cats.syntax.flatMap._
import cats.syntax.traverse._

import cats.instances.either._
import cats.instances.tuple._

import data.prelude._
import data.Cofree
import data.Free
import syntax.all._
import implicits.composedFunctor._

/**
* @groupname refolds Refolds
* @groupname folds Folds
* @groupname unfolds Unfolds
* @groupname exotic Exotic
*/
object scheme {

Expand Down Expand Up @@ -139,129 +129,45 @@ object scheme {
algebraM,
project.coalgebra.lift[M])


/** A variation of an anamorphism that lets you terminate any point of
* the recursion using a value of the original input type.
*
* One use case is to return cached/precomputed results during an
* unfold.
*
* @usecase def apo[F[_], A, R](coalgebra: RCoalgebra[R, F, A]): A => R
* @inheritdoc
*/
def apo[F[_]: Functor, A, R](
coalgebra: RCoalgebra[R, F, A]
)(implicit embed: Embed[F, R]): A => R =
hyloC(
embed.algebra.compose((frr: F[(R | R)]) => frr.map(_.merge)),
coalgebra)

/** A variation of a catamorphism that gives you access to the input value at
* every point in the computation.
*
* A paramorphism "eats its argument and keeps it too.
*
* This means each step has access to both the computed result
* value as well as the original value.
*
* @usecase def para[F[_], R, B](algebra: RAlgebra[R, F, B]): R => B
* @inheritdoc
*/
def para[F[_]: Functor, R, B](
algebra: RAlgebra[R, F, B]
)(implicit project: Project[F, R]): R => B =
hyloC(
algebra,
project.coalgebra.andThen(_.map(r => (r, r))))


/** Histomorphism
*
* @usecase def histo[F[_], R, B](algebra: CVAlgebra[F, B]): R => B
* @inheritdoc
*/
def histo[F[_]: Functor, R, B](
algebra: CVAlgebra[F, B]
def ghylo[SA, SB, F[_]: Functor, A, B](
algebra: F[SB] => B,
coalgebra: A => F[SA])(
gather: Gather[F, B, SB],
scatter: Scatter[F, A, SA]
): A => B =
a => algebra(coalgebra(a).map(
hylo[F, SA, SB](
fb => gather(algebra(fb), fb),
sa => scatter(sa).fold(coalgebra, identity))))

def gcata[S, F[_]: Functor, R, B](
algebra: F[S] => B)(
gather: Gather[F, B, S]
)(implicit project: Project[F, R]): R => B =
hylo[F, R, Cofree[F, B]](
fb => Cofree(algebra(fb), fb),
project.coalgebra
) andThen (_.head)

/** Futumorphism
*
* @usecase def futu[F[_], A, R](coalgebra: CVCoalgebra[F, A]): A => R
* @inheritdoc
*/
def futu[F[_]: Functor, A, R](
coalgebra: CVCoalgebra[F, A]
r => algebra(project.coalgebra(r).map(
hylo[F, R, S](
fb => gather(algebra(fb), fb),
project.coalgebra)))

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

/** A fusion refold of a futumorphism followed by a histomorphism
/** A petting zoo for wild and exotic animals we keep separate from
* the regulars in [[scheme]]. For their safety and yours.
*
* @group refolds
* @group exotic
*
* @usecase def chrono[F[_], A, B](algebra: CVAlgebra[F, B], coalgebra: CVCoalgebra[F, A]): A => B
* @inheritdoc
*/
def chrono[F[_]: Functor, A, B](
algebra: CVAlgebra[F, B],
coalgebra: CVCoalgebra[F, A]
): A => B =
hylo[F, Free[F, A], Cofree[F, B]](
fb => Cofree(algebra(fb), fb),
_.fold(coalgebra, identity)
) andThen (_.head) compose (Free.pure(_))

/** A fusion refold of an anamorphism followed by a histomorphism
*
* @group refolds
*
* @usecase def dyna[F[_], A, B](algebra: CVAlgebra[F, B], coalgebra: Coalgebra[F, A]): A => B
* @inheritdoc
* @groupname refolds Rambunctious Refolds
* @groupname folds Fantastic Folds
* @groupname unfolds Unusual Unfolds
*/
def dyna[F[_]: Functor, A, B](
algebra: CVAlgebra[F, B],
coalgebra: Coalgebra[F, A]
): A => B =
hylo[F, A, Cofree[F, B]](
fb => Cofree(algebra(fb), fb),
coalgebra
) andThen (_.head)

/** A generalized catamorphism
*
* @group folds
* @usecase def gcata[W[_], F[_], R, B](distFW: (F ∘ W)#λ ~> (W ∘ F)#λ, algebra: GAlgebra[W, F, B]): R => B
* @inheritdoc
*/
def gcata[W[_]: Comonad, F[_]: Functor, R, B](
distFW: (FW)#λ ~> (WF)#λ,
algebra: GAlgebra[W, F, B]
)(implicit project: Project[F, R]): R => B =
hylo[F, R, W[B]](
fwb => distFW(fwb.map(_.coflatten)).map(algebra),
project.coalgebra
) andThen (_.extract)

/** A generalized anamorphism
*
* @group unfolds
* @usecase def gana[W[_], F[_], A, R](distWF: (W ∘ F)#λ ~> (F ∘ W)#λ, coalgebra: Coalgebra[F, A]): A => R
* @inheritdoc
*/
def gana[W[_]: Monad, F[_]: Functor, A, R](
distWF: (WF)#λ ~> (FW)#λ,
coalgebra: Coalgebra[F, A]
)(implicit embed: Embed[F, R]): A => R =
hylo[F, W[A], R](
embed.algebra,
wa => distWF(wa.map(coalgebra))
) compose (_.pure[W])
object zoo extends Zoo

/** Convenience to specify the base constructor "shape" (such as `Fix`
* or `Cofree[?[_], Int]`) for recursion.
Expand Down Expand Up @@ -292,17 +198,6 @@ object scheme {
)(implicit embed: EmbedP[F], ev: TraverseP[F]): A => M[PatR[F]] =
scheme.anaM[M, PatF[F, ?], A, PatR[F]](coalgebraM)

def apo[F[_], A](
rcoalgebra: RCoalgebra[PatR[F], PatF[F, ?], A]
)(implicit embed: EmbedP[F], ev: FunctorP[F]): A => PatR[F] =
scheme.apo[PatF[F, ?], A, PatR[F]](rcoalgebra)

def futu[F[_], A](
cvcoalgebra: CVCoalgebra[PatF[F, ?], A]
)(implicit embed: EmbedP[F], ev: FunctorP[F]): A => PatR[F] =
scheme.futu[PatF[F, ?], A, PatR[F]](cvcoalgebra)


def cata[F[_], B](
algebra: Algebra[PatF[F, ?], B]
)(implicit project: ProjectP[F], ev: FunctorP[F]): PatR[F] => B =
Expand Down
Loading

0 comments on commit 887c471

Please sign in to comment.