Skip to content

Commit

Permalink
Add InvariantCartesian and FreeInvariant
Browse files Browse the repository at this point in the history
  • Loading branch information
OlivierBlanvillain committed Jan 31, 2016
1 parent 33642a0 commit 6c2fc6f
Show file tree
Hide file tree
Showing 15 changed files with 308 additions and 31 deletions.
41 changes: 41 additions & 0 deletions core/src/main/scala/cats/InvariantCartesian.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package cats

import cats.functor.Invariant
import simulacrum.typeclass

/**
* Invariant vesrion of a Cartesian.
*
* Must obey the laws defined in cats.laws.InvariantCartesianLaws.
*/
@typeclass trait InvariantCartesian[F[_]] extends Invariant[F] with Cartesian[F]

object InvariantCartesian extends AlgebraInvariantCartesianInstances

/**
* InvariantCartesian 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 AlgebraInvariantCartesianInstances {
implicit val invariantCartesianSemigroup: InvariantCartesian[Semigroup] = new InvariantCartesian[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)))
}
}

implicit val invariantCartesianMonoid: InvariantCartesian[Monoid] = new InvariantCartesian[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)))
}
}
}
62 changes: 62 additions & 0 deletions core/src/main/scala/cats/free/FreeInvariant.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package cats
package free

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

/** Invariant Cartesian for Free */
sealed abstract class FreeInvariant[F[_], A] extends Product with Serializable { self =>
import FreeInvariant.{FA, Zip, Imap, 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 `InvariantCartesian[G]` */
def foldMap[G[_]](nt: NaturalTransformation[F, G])(implicit im: InvariantCartesian[G]): G[A]
// Note that implementing a concrete `foldMap` here does not work because of
// `Zip extends G[(A, B)]`, which breaks the pattern matching on this.

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

/** Interpret this algebra into another InvariantCartesian */
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))
}
}
}

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

private final case class Suspend[F[_], A](fa: F[A]) extends FA[F, A] {
def foldMap[G[_]](nt: NaturalTransformation[F, G])(implicit im: InvariantCartesian[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: InvariantCartesian[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: InvariantCartesian[G]): G[B] =
im.imap(fa.foldMap(nt))(f)(g)
}

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

/** `FreeInvariant[S, ?]` has a FreeInvariant for any type constructor `S[_]`. */
implicit def freeInvariant[S[_]]: InvariantCartesian[FA[S, ?]] =
new InvariantCartesian[FA[S, ?]] {
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)
}
}
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
8 changes: 8 additions & 0 deletions docs/src/main/tut/cartesian.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
layout: default
title: "Cartesian"
section: "typeclasses"
source: "https://github.com/non/cats/blob/master/core/src/main/scala/cats/Cartesian.scala"
scaladoc: "#cats.Cartesian"
---
# Cartesian
10 changes: 10 additions & 0 deletions docs/src/main/tut/freeinvariant.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
layout: default
title: "FreeInvariants"
section: "data"
source: "https://github.com/non/cats/blob/master/core/src/main/scala/cats/free/FreeInvariant.scala"
scaladoc: "#cats.free.FreeInvariant"
---
# Free Invariant

`FreeInvariants` are the `Invariant` version of `FreeApplicative`. They differ from `FreeApplicative` by their ability to capture both the contruction and the deconstruction of data.
23 changes: 23 additions & 0 deletions docs/src/main/tut/invariantcartesian.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
layout: default
title: "InvariantCartesian"
section: "typeclasses"
source: "https://github.com/non/cats/blob/master/core/src/main/scala/cats/InvariantCartesian.scala"
scaladoc: "#cats.InvariantCartesian"
---
# Invariant Cartesian

`InvariantCartesian` combines the [`Invariant`](invariant.html) type class with the [`Cartesian`](cartesian.html) type class with no additional methods. As such, it provides both `imap` and `product` functions with the following signature:

```scala
def imap[A, B](fa: F[A])(f: A => B)(g: B => A): F[B]
def product[A, B](fa: F[A], fb: F[B]): F[(A, B)]
```

# Examples

# Monoid & Semigroup

[`Invariant`](invariant.html)

# Codecs
7 changes: 3 additions & 4 deletions laws/src/main/scala/cats/laws/CartesianLaws.scala
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
package cats
package laws

/**
* Laws that must be obeyed by any `cats.Cartesian`.
*/
trait CartesianLaws[F[_]] {

implicit def F: Cartesian[F]

def cartesianAssociativity[A, B, C](fa: F[A], fb: F[B], fc: F[C]): (F[(A, (B, C))], F[((A, B), C)]) =
(F.product(fa, F.product(fb, fc)), F.product(F.product(fa, fb), fc))

}

object CartesianLaws {

def apply[F[_]](implicit ev: Cartesian[F]): CartesianLaws[F] =
new CartesianLaws[F] { val F = ev }

}
14 changes: 14 additions & 0 deletions laws/src/main/scala/cats/laws/InvariantCartesianLaws.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package cats
package laws

/**
* Laws that must be obeyed by any `cats.InvariantCartesian`.
*/
trait InvariantCartesianLaws[F[_]] extends InvariantLaws[F] with CartesianLaws[F]{
override implicit def F: InvariantCartesian[F]
}

object InvariantCartesianLaws {
def apply[F[_]](implicit i: InvariantCartesian[F]): InvariantCartesianLaws[F] =
new InvariantCartesianLaws[F] { def F: InvariantCartesian[F] = i }
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cats
package laws
package discipline

import cats.laws.discipline.CartesianTests.Isomorphisms
import org.scalacheck.Arbitrary
import org.scalacheck.Prop
import Prop._
Expand All @@ -11,7 +12,7 @@ trait CartesianTests[F[_]] extends Laws {
def laws: CartesianLaws[F]

def cartesian[A : Arbitrary, B : Arbitrary, C : Arbitrary](implicit
iso: CartesianTests.Isomorphisms[F],
iso: Isomorphisms[F],
ArbFA: Arbitrary[F[A]],
ArbFB: Arbitrary[F[B]],
ArbFC: Arbitrary[F[C]],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package cats
package laws
package discipline

import cats.laws.discipline.CartesianTests.Isomorphisms
import org.scalacheck.Arbitrary

trait InvariantCartesianTests[F[_]] extends InvariantTests[F] with CartesianTests[F] {
def laws: InvariantCartesianLaws[F]

def invariantCartesian[A : Arbitrary, B : Arbitrary, C : Arbitrary](implicit
ArbFA: Arbitrary[F[A]],
ArbFB: Arbitrary[F[B]],
ArbFC: Arbitrary[F[C]],
EqFABC: Eq[F[(A, B, C)]],
iso: Isomorphisms[F],
EqFA: Eq[F[A]],
EqFC: Eq[F[C]]
): RuleSet =
new RuleSet {
val name = "invariantCartesian"
val parents = Seq(invariant[A, B, C], cartesian[A, B, C])
val bases = Seq.empty
val props = Seq.empty
}
}

object InvariantCartesianTests {
def apply[F[_] : InvariantCartesian]: InvariantCartesianTests[F] =
new InvariantCartesianTests[F] { def laws: InvariantCartesianLaws[F] = InvariantCartesianLaws[F] }
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ trait InvariantTests[F[_]] extends Laws {
EqFA: Eq[F[A]],
EqFC: Eq[F[C]]
): RuleSet = {

new DefaultRuleSet(
name = "invariant",
parent = None,
Expand Down
17 changes: 6 additions & 11 deletions laws/src/main/scala/cats/laws/discipline/MonoidKTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,12 @@ trait MonoidKTests[F[_]] extends SemigroupKTests[F] {
def monoidK[A: Arbitrary](implicit
ArbFA: Arbitrary[F[A]],
EqFA: Eq[F[A]]
): RuleSet = {
new RuleSet {
val name = "monoidK"
val bases = Nil
val parents = Seq(semigroupK[A])
val props = Seq(
"monoidK left identity" -> forAll(laws.monoidKLeftIdentity[A] _),
"monoidK right identity" -> forAll(laws.monoidKRightIdentity[A] _)
)
}
}
): RuleSet =
new DefaultRuleSet(
"monoidK",
Some(semigroupK[A]),
"monoidK left identity" -> forAll(laws.monoidKLeftIdentity[A] _),
"monoidK right identity" -> forAll(laws.monoidKRightIdentity[A] _))
}

object MonoidKTests {
Expand Down
15 changes: 5 additions & 10 deletions laws/src/main/scala/cats/laws/discipline/SemigroupKTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,11 @@ trait SemigroupKTests[F[_]] extends Laws {
def semigroupK[A: Arbitrary](implicit
ArbFA: Arbitrary[F[A]],
EqFA: Eq[F[A]]
): RuleSet = {
new RuleSet {
val name = "semigroupK"
val bases = Nil
val parents = Nil
val props = Seq(
"semigroupK associative" -> forAll(laws.semigroupKAssociative[A] _)
)
}
}
): RuleSet =
new DefaultRuleSet(
"semigroupK",
None,
"semigroupK associative" -> forAll(laws.semigroupKAssociative[A] _))
}

object SemigroupKTests {
Expand Down
10 changes: 7 additions & 3 deletions tests/src/test/scala/cats/tests/AlgebraInvariantTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package cats
package tests

import cats.functor.Invariant
import cats.laws.discipline.{InvariantTests, SerializableTests}
import cats.laws.discipline.{InvariantTests, InvariantCartesianTests, SerializableTests}
import cats.laws.discipline.eq._

import org.scalacheck.{Arbitrary, Gen}
Expand All @@ -11,13 +11,11 @@ class AlgebraInvariantTests extends CatsSuite {

val intMultiplication: Monoid[Int] = new Monoid[Int] {
val empty = 1

def combine(x: Int, y: Int): Int = x * y
}

val maxInt: Monoid[Int] = new Monoid[Int] {
val empty = Int.MinValue

def combine(x: Int, y: Int): Int = if (x > y) x else y
}

Expand All @@ -35,4 +33,10 @@ class AlgebraInvariantTests extends CatsSuite {

checkAll("Invariant[Monoid]", InvariantTests[Monoid].invariant[Int, Int, Int])
checkAll("Invariant[Monoid]", SerializableTests.serializable(Invariant[Monoid]))

checkAll("InvariantCartesian[Semigroup]", InvariantCartesianTests[Semigroup].invariantCartesian[Int, Int, Int])
checkAll("InvariantCartesian[Semigroup]", SerializableTests.serializable(InvariantCartesian[Semigroup]))

checkAll("InvariantCartesian[Monoid]", InvariantCartesianTests[Monoid].invariantCartesian[Int, Int, Int])
checkAll("InvariantCartesian[Monoid]", SerializableTests.serializable(InvariantCartesian[Monoid]))
}
Loading

0 comments on commit 6c2fc6f

Please sign in to comment.