Skip to content

Commit

Permalink
Add InvariantMonoidal and FreeInvariantMonoidal
Browse files Browse the repository at this point in the history
  • Loading branch information
OlivierBlanvillain committed Feb 13, 2016
1 parent 1d21405 commit 51456ec
Show file tree
Hide file tree
Showing 12 changed files with 635 additions and 18 deletions.
11 changes: 10 additions & 1 deletion core/src/main/scala/cats/Cartesian.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,13 @@ import simulacrum.typeclass
def product[A, B](fa: F[A], fb: F[B]): F[(A, B)]
}

object Cartesian extends CartesianArityFunctions
object Cartesian extends CartesianArityFunctions with AlgebrCartesianInstances

/**
* Cartesian instances for types that are housed in Algebra and therefore
* can't have instances for Cats type classes in their companion objects.
*/
private[cats] sealed trait AlgebrCartesianInstances {
implicit val invariantSemigroup: Cartesian[Semigroup] = InvariantMonoidal.invariantMonoidalSemigroup
implicit val invariantMonoid: Cartesian[Monoid] = InvariantMonoidal.invariantMonoidalMonoid
}
52 changes: 52 additions & 0 deletions core/src/main/scala/cats/InvariantMonoidal.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package cats

import cats.functor.Invariant
import simulacrum.typeclass

/**
* Invariant version of a Monoidal.
*
* Must obey the laws defined in cats.laws.InvariantMonoidalLaws.
*/
@typeclass trait InvariantMonoidal[F[_]] extends Invariant[F] with Cartesian[F] {
def pure[A](a: A): F[A]
}

object InvariantMonoidal extends AlgebraInvariantMonoidalInstances

/**
* InvariantMonoidal instances for types that are housed in Algebra and therefore
* can't have instances for Cats type classes in their companion objects.
*/
private[cats] trait AlgebraInvariantMonoidalInstances {
implicit val invariantMonoidalSemigroup: InvariantMonoidal[Semigroup] = new InvariantMonoidal[Semigroup] {
def product[A, B](fa: Semigroup[A], fb: Semigroup[B]): Semigroup[(A, B)] = new Semigroup[(A, B)] {
def combine(x: (A, B), y: (A, B)): (A, B) = fa.combine(x._1, y._1) -> fb.combine(x._2, y._2)
}

def imap[A, B](fa: Semigroup[A])(f: A => B)(g: B => A): Semigroup[B] = new Semigroup[B] {
def combine(x: B, y: B): B = f(fa.combine(g(x), g(y)))
}

def pure[A](a: A): Semigroup[A] = new Semigroup[A] {
def combine(x: A, y: A): A = a
}
}

implicit val invariantMonoidalMonoid: InvariantMonoidal[Monoid] = new InvariantMonoidal[Monoid] {
def product[A, B](fa: Monoid[A], fb: Monoid[B]): Monoid[(A, B)] = new Monoid[(A, B)] {
val empty = fa.empty -> fb.empty
def combine(x: (A, B), y: (A, B)): (A, B) = fa.combine(x._1, y._1) -> fb.combine(x._2, y._2)
}

def imap[A, B](fa: Monoid[A])(f: A => B)(g: B => A): Monoid[B] = new Monoid[B] {
val empty = f(fa.empty)
def combine(x: B, y: B): B = f(fa.combine(g(x), g(y)))
}

def pure[A](a: A): Monoid[A] = new Monoid[A] {
val empty = a
def combine(x: A, y: A): A = a
}
}
}
11 changes: 11 additions & 0 deletions core/src/main/scala/cats/data/Const.scala
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,17 @@ private[data] sealed abstract class ConstInstances0 extends ConstInstances1 {
}

private[data] sealed abstract class ConstInstances1 {
implicit def contInvariantMonoidal[C: Monoid]: InvariantMonoidal[Const[C, ?]] = new InvariantMonoidal[Const[C, ?]] {
def pure[A](a: A): Const[C, A] =
Const.empty

def imap[A, B](fa: Const[C, A])(f: A => B)(g: B => A): Const[C, B] =
fa.retag[B]

def product[A, B](fa: Const[C, A],fb: Const[C, B]): Const[C, (A, B)] =
fa.retag[(A, B)] combine fb.retag[(A, B)]
}

implicit def constEq[A: Eq, B]: Eq[Const[A, B]] = new Eq[Const[A, B]] {
def eqv(x: Const[A, B], y: Const[A, B]): Boolean =
x === y
Expand Down
79 changes: 79 additions & 0 deletions core/src/main/scala/cats/free/FreeInvariantMonoidal.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package cats
package free

import cats.arrow.NaturalTransformation
import cats.data.Const

/**
* Invariant Monoidal for Free
*/
sealed abstract class FreeInvariantMonoidal[F[_], A] extends Product with Serializable { self =>
import FreeInvariantMonoidal.{FA, Zip, Imap, Pure, lift}

def imap[B](f: A => B)(g: B => A): FA[F, B] =
Imap(this, f, g)

def product[B](fb: FA[F, B]): FA[F, (A, B)] =
Zip(this, fb)

/** Interprets/Runs the sequence of operations using the semantics of `InvariantMonoidal[G]` */
def foldMap[G[_]](nt: NaturalTransformation[F, G])(implicit im: InvariantMonoidal[G]): G[A]
// Note that implementing a concrete `foldMap` here does not work because
// `Zip extends G[(A, B)]` confuses the type inferance when pattern matching on `this`.

/** Interpret/run the operations using the semantics of `InvariantMonoidal[F]`. */
final def fold(implicit F: InvariantMonoidal[F]): F[A] =
foldMap(NaturalTransformation.id[F])

/** Interpret this algebra into another InvariantMonoidal */
final def compile[G[_]](f: F ~> G): FA[G, A] =
foldMap[FA[G, ?]] {
new NaturalTransformation[F, FA[G, ?]] {
def apply[B](fa: F[B]): FA[G, B] = lift(f(fa))
}
}

/** Interpret this algebra into a Monoid */
final def analyze[M: Monoid](f: F ~> λ[α => M]): M =
foldMap[Const[M, ?]](new (F ~> Const[M, ?]) {
def apply[X](x: F[X]): Const[M, X] = Const(f(x))
}).getConst
}

object FreeInvariantMonoidal {
type FA[F[_], A] = FreeInvariantMonoidal[F, A]

private final case class Pure[F[_], A](a: A) extends FA[F, A] {
def foldMap[G[_]](nt: NaturalTransformation[F, G])(implicit im: InvariantMonoidal[G]): G[A] =
im.pure(a)
}

private final case class Suspend[F[_], A](fa: F[A]) extends FA[F, A] {
def foldMap[G[_]](nt: NaturalTransformation[F, G])(implicit im: InvariantMonoidal[G]): G[A] =
nt(fa)
}

private final case class Zip[F[_], A, B](fa: FA[F, A], fb: FA[F, B]) extends FA[F, (A, B)] {
def foldMap[G[_]](nt: NaturalTransformation[F, G])(implicit im: InvariantMonoidal[G]): G[(A, B)] =
im.product(fa.foldMap(nt), fb.foldMap(nt))
}

private final case class Imap[F[_], A, B](fa: FA[F, A], f: A => B, g: B => A) extends FA[F, B] {
def foldMap[G[_]](nt: NaturalTransformation[F, G])(implicit im: InvariantMonoidal[G]): G[B] =
im.imap(fa.foldMap(nt))(f)(g)
}

def pure[F[_], A](a: A): FA[F, A] =
Pure(a)

def lift[F[_], A](fa: F[A]): FA[F, A] =
Suspend(fa)

/** `FreeInvariantMonoidal[S, ?]` has a FreeInvariantMonoidal for any type constructor `S[_]`. */
implicit def freeInvariant[S[_]]: InvariantMonoidal[FA[S, ?]] =
new InvariantMonoidal[FA[S, ?]] {
def pure[A](a: A): FA[S, A] = FreeInvariantMonoidal.pure(a)
def imap[A, B](fa: FA[S, A])(f: A => B)(g: B => A): FA[S, B] = fa.imap(f)(g)
def product[A, B](fa: FA[S, A], fb: FA[S, B]): FA[S, (A, B)] = fa.product(fb)
}
}
17 changes: 2 additions & 15 deletions core/src/main/scala/cats/functor/Invariant.scala
Original file line number Diff line number Diff line change
Expand Up @@ -65,19 +65,6 @@ object Invariant extends AlgebraInvariantInstances {
* can't have instances for Cats type classes in their companion objects.
*/
private[functor] sealed trait AlgebraInvariantInstances {

implicit val invariantSemigroup: Invariant[Semigroup] = new Invariant[Semigroup] {
def imap[A, B](fa: Semigroup[A])(f: A => B)(g: B => A): Semigroup[B] = new Semigroup[B] {

def combine(x: B, y: B): B = f(fa.combine(g(x), g(y)))
}
}

implicit val invariantMonoid: Invariant[Monoid] = new Invariant[Monoid] {
def imap[A, B](fa: Monoid[A])(f: A => B)(g: B => A): Monoid[B] = new Monoid[B] {
val empty = f(fa.empty)

def combine(x: B, y: B): B = f(fa.combine(g(x), g(y)))
}
}
implicit val invariantSemigroup: Invariant[Semigroup] = InvariantMonoidal.invariantMonoidalSemigroup
implicit val invariantMonoid: Invariant[Monoid] = InvariantMonoidal.invariantMonoidalMonoid
}
Loading

0 comments on commit 51456ec

Please sign in to comment.