Skip to content

Commit

Permalink
Merge pull request #845 from OlivierBlanvillain/free-invariant
Browse files Browse the repository at this point in the history
Add InvariantMonoidal and FreeInvariantMonoidal
  • Loading branch information
ceedubs authored Jun 23, 2016
2 parents 8ff5c60 + 5d4170b commit c869d7b
Show file tree
Hide file tree
Showing 28 changed files with 669 additions and 77 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 KernelCartesianInstances

/**
* Cartesian instances for types that are housed in Kernel and therefore
* can't have instances for Cats type classes in their companion objects.
*/
private[cats] sealed trait KernelCartesianInstances {
implicit val catsInvariantSemigroup: Cartesian[Semigroup] = InvariantMonoidal.catsInvariantMonoidalSemigroup
implicit val catsInvariantMonoid: Cartesian[Monoid] = InvariantMonoidal.catsInvariantMonoidalMonoid
}
23 changes: 11 additions & 12 deletions core/src/main/scala/cats/FlatMap.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@ package cats
import simulacrum.typeclass

/**
* FlatMap type class gives us flatMap, which allows us to have a value
* in a context (F[A]) and then feed that into a function that takes
* a normal value and returns a value in a context (A => F[B]).
* FlatMap type class gives us flatMap, which allows us to have a value
* in a context (F[A]) and then feed that into a function that takes
* a normal value and returns a value in a context (A => F[B]).
*
* One motivation for separating this out from Monad is that there are
* situations where we can implement flatMap but not pure. For example,
* we can implement map or flatMap that transforms the values of Map[K, ?],
* but we can't implement pure (because we wouldn't know what key to use
* when instantiating the new Map).
* One motivation for separating this out from Monad is that there are
* situations where we can implement flatMap but not pure. For example,
* we can implement map or flatMap that transforms the values of Map[K, ?],
* but we can't implement pure (because we wouldn't know what key to use
* when instantiating the new Map).
*
* @see See [[https://github.com/typelevel/cats/issues/3]] for some discussion.
* @see See [[https://github.com/typelevel/cats/issues/3]] for some discussion.
*
* Must obey the laws defined in cats.laws.FlatMapLaws.
*/
Expand All @@ -33,15 +33,14 @@ import simulacrum.typeclass
flatMap(fa)(a => map(fb)(b => (a, b)))

/**
* Pair `A` with the result of function application.
* Pair `A` with the result of function application.
*/
def mproduct[A, B](fa: F[A])(f: A => F[B]): F[(A, B)] =
flatMap(fa)(a => map(f(a))((a, _)))

/**
* `if` lifted into monad.
*/
def ifM[B](fa: F[Boolean])(ifTrue: => F[B], ifFalse: => F[B]): F[B] = {
def ifM[B](fa: F[Boolean])(ifTrue: => F[B], ifFalse: => F[B]): F[B] =
flatMap(fa)(if (_) ifTrue else ifFalse)
}
}
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 KernelInvariantMonoidalInstances

/**
* InvariantMonoidal instances for types that are housed in cats.kernel and therefore
* can't have instances for this type class in their companion objects.
*/
private[cats] trait KernelInvariantMonoidalInstances {
implicit val catsInvariantMonoidalSemigroup: 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 catsInvariantMonoidalMonoid: 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
}
}
}
1 change: 0 additions & 1 deletion core/src/main/scala/cats/TransLift.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package cats


/**
* A type class which abstracts over the ability to lift an M[A] into a
* MonadTransformer
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/scala/cats/Traverse.scala
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ import simulacrum.typeclass
* Behaves just like sequence, but uses [[Unapply]] to find the
* Applicative instance for G.
*/
def sequenceU[GA](fga: F[GA])(implicit U: Unapply[Applicative,GA]): U.M[F[U.A]] =
def sequenceU[GA](fga: F[GA])(implicit U: Unapply[Applicative, GA]): U.M[F[U.A]] =
traverse(fga)(U.subst)(U.TC)

def compose[G[_]: Traverse]: Traverse[λ[α => F[G[α]]]] =
Expand Down
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 @@ -107,6 +107,17 @@ private[data] sealed abstract class ConstInstances0 extends ConstInstances1 {
}

private[data] sealed abstract class ConstInstances1 {
implicit def catsConstInvariantMonoidal[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 catsDataEqForConst[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
6 changes: 3 additions & 3 deletions core/src/main/scala/cats/data/Xor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,13 @@ sealed abstract class Xor[+A, +B] extends Product with Serializable {

def toTry(implicit ev: A <:< Throwable): Try[B] = fold(a => Failure(ev(a)), Success(_))

def toValidated: Validated[A,B] = fold(Validated.Invalid.apply, Validated.Valid.apply)
def toValidated: Validated[A, B] = fold(Validated.Invalid.apply, Validated.Valid.apply)

/** Returns a [[ValidatedNel]] representation of this disjunction with the `Left` value
* as a single element on the `Invalid` side of the [[NonEmptyList]]. */
def toValidatedNel[AA >: A]: ValidatedNel[AA,B] = fold(Validated.invalidNel, Validated.valid)
def toValidatedNel[AA >: A]: ValidatedNel[AA, B] = fold(Validated.invalidNel, Validated.valid)

def withValidated[AA,BB](f: Validated[A,B] => Validated[AA,BB]): AA Xor BB =
def withValidated[AA, BB](f: Validated[A, B] => Validated[AA, BB]): AA Xor BB =
f(toValidated).toXor

def to[F[_], BB >: B](implicit F: Alternative[F]): F[BB] =
Expand Down
25 changes: 6 additions & 19 deletions core/src/main/scala/cats/functor/Invariant.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,26 +28,13 @@ import simulacrum.typeclass
}
}

object Invariant extends AlgebraInvariantInstances
object Invariant extends KernelInvariantInstances

/**
* Invariant instances for types that are housed in Algebra and therefore
* can't have instances for Cats type classes in their companion objects.
* Invariant instances for types that are housed in cats.kernel and therefore
* can't have instances for this type class in their companion objects.
*/
private[functor] sealed trait AlgebraInvariantInstances {

implicit val catsFunctorInvariantForSemigroup: 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 catsFunctorInvariantForMonoid: 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)))
}
}
private[functor] sealed trait KernelInvariantInstances {
implicit val catsFunctorInvariantForSemigroup: Invariant[Semigroup] = InvariantMonoidal.catsInvariantMonoidalSemigroup
implicit val catsFunctorInvariantForMonoid: Invariant[Monoid] = InvariantMonoidal.catsInvariantMonoidalMonoid
}
2 changes: 1 addition & 1 deletion core/src/main/scala/cats/syntax/bifunctor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ trait BifunctorSyntax {
}

final class BifunctorOps[F[_, _], A, B](fab: F[A, B])(implicit F: Bifunctor[F]) {
def bimap[C, D](f: A => C, g: B => D): F[C,D] = F.bimap(fab)(f,g)
def bimap[C, D](f: A => C, g: B => D): F[C, D] = F.bimap(fab)(f,g)

def leftMap[C](f: A => C): F[C, B] = F.leftMap(fab)(f)

Expand Down
2 changes: 1 addition & 1 deletion docs/src/main/tut/applicative.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ scaladoc: "#cats.Applicative"
`pure`:

```scala
def pure[A](x: A): F[A]
def pure[A](x: A): F[A]
````

This method takes any value and returns the value in the context of
Expand Down
4 changes: 2 additions & 2 deletions docs/src/main/tut/apply.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ scaladoc: "#cats.Apply"
function) with a new function `ap`. The `ap` function is similar to `map`
in that we are transforming a value in a context (a context being the `F` in `F[A]`;
a context can be `Option`, `List` or `Future` for example).
However, the difference between `ap` and `map` is that for `ap` the function that
However, the difference between `ap` and `map` is that for `ap` the function that
takes care of the transformation is of type `F[A => B]`, whereas for `map` it is `A => B`:

```tut:silent
Expand Down Expand Up @@ -71,7 +71,7 @@ Apply[Option].ap(None)(None)

### ap2, ap3, etc

`Apply` also offers variants of `ap`. The functions `apN` (for `N` between `2` and `22`)
`Apply` also offers variants of `ap`. The functions `apN` (for `N` between `2` and `22`)
accept `N` arguments where `ap` accepts `1`:

For example:
Expand Down
2 changes: 1 addition & 1 deletion docs/src/main/tut/invariant.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def imap[A, B](fa: F[A])(f: A => B)(g: B => A): F[B]
Every covariant (as well as [contravariant](contravariant.html)) functor gives rise to an invariant
functor, by ignoring the `g` (or in case of contravariance, `f`) function.

Examples for instances of `Invariant` are `Semigroup` and `Monoid`, in
Examples for instances of `Invariant` are [`Semigroup`](semigroup.md) and [`Monoid`](monoid.md), in
the following we will explain why this is the case using `Semigroup`, the
reasoning for `Monoid` is analogous.

Expand Down
Loading

0 comments on commit c869d7b

Please sign in to comment.