Skip to content

Commit

Permalink
Adding more contravariant instances
Browse files Browse the repository at this point in the history
  • Loading branch information
DavidGregory084 committed Mar 28, 2016
1 parent 58362c2 commit ab31ab1
Show file tree
Hide file tree
Showing 13 changed files with 139 additions and 15 deletions.
7 changes: 6 additions & 1 deletion core/src/main/scala/cats/data/Cokleisli.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package cats
package data

import cats.arrow.{Arrow, Split}
import cats.functor.Profunctor
import cats.functor.{Contravariant, Profunctor}
import cats.{CoflatMap, Comonad, Functor, Monad}

/**
Expand Down Expand Up @@ -71,6 +71,11 @@ private[data] sealed abstract class CokleisliInstances0 {

implicit def cokleisliSemigroupK[F[_]](implicit ev: CoflatMap[F]): SemigroupK[Lambda[A => Cokleisli[F, A, A]]] =
new CokleisliSemigroupK[F] { def F: CoflatMap[F] = ev }

implicit def cokleisliContravariant[F[_]: Functor, A]: Contravariant[Cokleisli[F, ?, A]] =
new Contravariant[Cokleisli[F, ?, A]] {
def contramap[B, C](fbc: Cokleisli[F, B, A])(f: C => B): Cokleisli[F, C, A] = fbc.lmap(f)
}
}

private trait CokleisliArrow[F[_]] extends Arrow[Cokleisli[F, ?, ?]] with CokleisliSplit[F] with CokleisliProfunctor[F] {
Expand Down
13 changes: 13 additions & 0 deletions core/src/main/scala/cats/data/Func.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package cats
package data

import cats.functor.Contravariant

/**
* [[Func]] is a function `A => F[B]`.
*
Expand Down Expand Up @@ -50,6 +52,11 @@ private[data] abstract class FuncInstances1 {
new FuncFunctor[F, C] {
def F: Functor[F] = FF
}

implicit def funcContravariant[F[_], C](implicit FC: Contravariant[F]): Contravariant[Lambda[X => Func[F, X, C]]] =
new FuncContravariant[F, C] {
def F: Contravariant[F] = FC
}
}

sealed trait FuncFunctor[F[_], C] extends Functor[Lambda[X => Func[F, C, X]]] {
Expand All @@ -58,6 +65,12 @@ sealed trait FuncFunctor[F[_], C] extends Functor[Lambda[X => Func[F, C, X]]] {
fa.map(f)(F)
}

sealed trait FuncContravariant[F[_], C] extends Contravariant[Lambda[X => Func[F, X, C]]] {
def F: Contravariant[F]
def contramap[A, B](fa: Func[F, A, C])(f: B => A): Func[F, B, C] =
Func.func(a => fa.run(f(a)))
}

sealed trait FuncApply[F[_], C] extends Apply[Lambda[X => Func[F, C, X]]] with FuncFunctor[F, C] {
def F: Apply[F]
def ap[A, B](f: Func[F, C, A => B])(fa: Func[F, C, A]): Func[F, C, B] =
Expand Down
12 changes: 12 additions & 0 deletions core/src/main/scala/cats/data/Prod.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package cats
package data

import cats.functor.Contravariant

/**
* [[Prod]] is a product to two independent functor values.
*
Expand Down Expand Up @@ -66,6 +68,10 @@ private[data] sealed abstract class ProdInstances4 {
def F: Functor[F] = FF
def G: Functor[G] = GG
}
implicit def prodContravariant[F[_], G[_]](implicit FC: Contravariant[F], GC: Contravariant[G]): Contravariant[Lambda[X => Prod[F, G, X]]] = new ProdContravariant[F, G] {
def F: Contravariant[F] = FC
def G: Contravariant[G] = GC
}
}

sealed trait ProdFunctor[F[_], G[_]] extends Functor[Lambda[X => Prod[F, G, X]]] {
Expand All @@ -74,6 +80,12 @@ sealed trait ProdFunctor[F[_], G[_]] extends Functor[Lambda[X => Prod[F, G, X]]]
def map[A, B](fa: Prod[F, G, A])(f: A => B): Prod[F, G, B] = Prod(F.map(fa.first)(f), G.map(fa.second)(f))
}

sealed trait ProdContravariant[F[_], G[_]] extends Contravariant[Lambda[X => Prod[F, G, X]]] {
def F: Contravariant[F]
def G: Contravariant[G]
def contramap[A, B](fa: Prod[F, G, A])(f: B => A): Prod[F, G, B] = Prod(F.contramap(fa.first)(f), G.contramap(fa.second)(f))
}

sealed trait ProdApply[F[_], G[_]] extends Apply[Lambda[X => Prod[F, G, X]]] with ProdFunctor[F, G] {
def F: Apply[F]
def G: Apply[G]
Expand Down
17 changes: 16 additions & 1 deletion core/src/main/scala/cats/data/WriterT.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package cats
package data

import cats.functor.Bifunctor
import cats.functor.{Bifunctor, Contravariant}

final case class WriterT[F[_], L, V](run: F[(L, V)]) {
def written(implicit functorF: Functor[F]): F[L] =
Expand All @@ -21,6 +21,11 @@ final case class WriterT[F[_], L, V](run: F[(L, V)]) {
functorF.map(run) { z => (z._1, fn(z._2)) }
}

def contramap[Z](fn: Z => V)(implicit F: Contravariant[F]): WriterT[F, L, Z] =
WriterT {
F.contramap(run) { z => (z._1, fn(z._2)) }
}

def flatMap[U](f: V => WriterT[F, L, U])(implicit flatMapF: FlatMap[F], semigroupL: Semigroup[L]): WriterT[F, L, U] =
WriterT {
flatMapF.flatMap(run) { lv =>
Expand Down Expand Up @@ -154,6 +159,9 @@ private[data] sealed abstract class WriterTInstances7 {
implicit def writerTFunctor[F[_], L](implicit F: Functor[F]): Functor[WriterT[F, L, ?]] = new WriterTFunctor[F, L] {
implicit val F0: Functor[F] = F
}
implicit def writerTContravariant[F[_], L](implicit F: Contravariant[F]): Contravariant[WriterT[F, L, ?]] = new WriterTContravariant[F, L] {
implicit val F0: Contravariant[F] = F
}
}

private[data] sealed trait WriterTFunctor[F[_], L] extends Functor[WriterT[F, L, ?]] {
Expand All @@ -163,6 +171,13 @@ private[data] sealed trait WriterTFunctor[F[_], L] extends Functor[WriterT[F, L,
fa.map(f)
}

private[data] sealed trait WriterTContravariant[F[_], L] extends Contravariant[WriterT[F, L, ?]] {
implicit def F0: Contravariant[F]

def contramap[A, B](fa: WriterT[F, L, A])(f: B => A): WriterT[F, L, B] =
fa.contramap(f)
}

private[data] sealed trait WriterTApply[F[_], L] extends WriterTFunctor[F, L] with Apply[WriterT[F, L, ?]] {
override implicit def F0: Apply[F]
implicit def L0: Semigroup[L]
Expand Down
10 changes: 10 additions & 0 deletions core/src/main/scala/cats/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,14 @@ package object cats {
val Semigroup = algebra.Semigroup
val Monoid = algebra.Monoid
val Group = algebra.Group

implicit val partialOrderInstances: cats.functor.Contravariant[PartialOrder] =
new cats.functor.Contravariant[PartialOrder] {
def contramap[A, B](fa: PartialOrder[A])(f: B => A): PartialOrder[B] = fa.on(f)
}

implicit val orderInstances: cats.functor.Contravariant[Order] =
new cats.functor.Contravariant[Order] {
def contramap[A, B](fa: Order[A])(f: B => A): Order[B] = fa.on(f)
}
}
13 changes: 13 additions & 0 deletions laws/src/main/scala/cats/laws/discipline/Arbitrary.scala
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,19 @@ object arbitrary extends ArbitraryInstances0 {
implicit def showArbitrary[A: Arbitrary]: Arbitrary[Show[A]] =
Arbitrary(Show.fromToString[A])

implicit def partialOrderArbitrary[A: Arbitrary]: Arbitrary[PartialOrder[A]] =
Arbitrary(Gen.oneOf(
PartialOrder.from[A]((_: A, _: A) => Double.NaN),
PartialOrder.from[A]((_: A, _: A) => -1.0),
PartialOrder.from[A]((_: A, _: A) => 0.0),
PartialOrder.from[A]((_: A, _: A) => 1.0)))

implicit def orderArbitrary[A: Arbitrary]: Arbitrary[Order[A]] =
Arbitrary(Gen.oneOf(
Order.from[A]((_: A, _: A) => -1),
Order.from[A]((_: A, _: A) => 0),
Order.from[A]((_: A, _: A) => 1)))

implicit def function0Arbitrary[A: Arbitrary]: Arbitrary[() => A] =
Arbitrary(getArbitrary[A].map(() => _))
}
Expand Down
33 changes: 33 additions & 0 deletions laws/src/main/scala/cats/laws/discipline/Eq.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,39 @@ object eq {
}
}

/**
* Create an approximation of Eq[PartialOrder[A]] by generating 100 values for A
* and comparing the application of the two compare functions
*/
implicit def partialOrderEq[A](implicit arbA: Arbitrary[(A, A)], optIntEq: Eq[Option[Int]]): Eq[PartialOrder[A]] = new Eq[PartialOrder[A]] {
def eqv(f: PartialOrder[A], g: PartialOrder[A]): Boolean = {
val samples = List.fill(100)(arbA.arbitrary.sample).collect {
case Some(a) => a
case None => sys.error("Could not generate arbitrary values to compare two PartialOrder[A]")
}
samples.forall {
case (l, r) => optIntEq.eqv(f.tryCompare(l, r), g.tryCompare(l, r))
}
}
}

/**
* Create an approximation of Eq[Order[A]] by generating 100 values for A
* and comparing the application of the two compare functions
*/
implicit def orderEq[A](implicit arbA: Arbitrary[(A, A)], intEq: Eq[Int]): Eq[Order[A]] = new Eq[Order[A]] {
def eqv(f: Order[A], g: Order[A]): Boolean = {
val samples = List.fill(100)(arbA.arbitrary.sample).collect {
case Some(a) => a
case None => sys.error("Could not generate arbitrary values to compare two Order[A]")
}
samples.forall {
case (l, r) => intEq.eqv(f.compare(l, r), g.compare(l, r))
}
}
}


/** Create an approximation of Eq[Show[A]] by using function1Eq[A, String] */
implicit def showEq[A: Arbitrary]: Eq[Show[A]] =
Eq.by[Show[A], A => String] { showInstance =>
Expand Down
15 changes: 15 additions & 0 deletions tests/src/test/scala/cats/tests/AlgebraContravariantTests.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package cats
package tests

import cats.functor.Contravariant
import cats.laws.discipline.arbitrary._
import cats.laws.discipline.{ContravariantTests, SerializableTests}
import cats.laws.discipline.eq._

class AlgebraContravariantTests extends CatsSuite {
checkAll("Contravariant[PartialOrder]", ContravariantTests[PartialOrder].contravariant[Int, Int, Int])
checkAll("Contravariant[PartialOrder]", SerializableTests.serializable(Contravariant[PartialOrder]))

checkAll("Contravariant[Order]", ContravariantTests[Order].contravariant[Int, Int, Int])
checkAll("Contravariant[Order]", SerializableTests.serializable(Contravariant[Order]))
}
5 changes: 4 additions & 1 deletion tests/src/test/scala/cats/tests/CokleisliTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package tests

import cats.arrow.{Arrow, Split}
import cats.data.{Cokleisli, NonEmptyList}
import cats.functor.Profunctor
import cats.functor.{Contravariant, Profunctor}
import cats.laws.discipline._
import cats.laws.discipline.arbitrary._
import cats.laws.discipline.eq._
Expand Down Expand Up @@ -32,6 +32,9 @@ class CokleisliTests extends SlowCatsSuite {
checkAll("Cokleisli[Option, Int, Int]", SplitTests[Cokleisli[Option, ?, ?]].split[Int, Int, Int, Int, Int, Int])
checkAll("Split[Cokleisli[Option, ?, ?]", SerializableTests.serializable(Split[Cokleisli[Option, ?, ?]]))

checkAll("Cokleisli[Option, Int, Int]", ContravariantTests[Cokleisli[Option, ?, Int]].contravariant[Int, Int, Int])
checkAll("Contravariant[Cokleisli[Option, ?, Int]]", SerializableTests.serializable(Contravariant[Cokleisli[Option, ?, Int]]))

{
// Ceremony to help scalac to do the right thing, see also #267.
type CokleisliNEL[A, B] = Cokleisli[NonEmptyList, A, B]
Expand Down
12 changes: 1 addition & 11 deletions tests/src/test/scala/cats/tests/CoproductTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import cats.data.Coproduct
import cats.functor.Contravariant
import cats.laws.discipline._
import cats.laws.discipline.arbitrary._
import org.scalacheck.Arbitrary
import cats.laws.discipline.eq._

class CoproductTests extends CatsSuite {

Expand All @@ -32,16 +32,6 @@ class CoproductTests extends CatsSuite {
checkAll("Coproduct[Option, Option, Int]", OrderLaws[Coproduct[Option, Option, Int]].eqv)
checkAll("Eq[Coproduct[Option, Option, Int]]", SerializableTests.serializable(Eq[Coproduct[Option, Option, Int]]))

implicit def showEq[A](implicit arbA: Arbitrary[A], stringEq: Eq[String]): Eq[Show[A]] = new Eq[Show[A]] {
def eqv(f: Show[A], g: Show[A]): Boolean = {
val samples = List.fill(100)(arbA.arbitrary.sample).collect {
case Some(a) => a
case None => sys.error("Could not generate arbitrary values to compare two Show[A]")
}
samples.forall(s => stringEq.eqv(f.show(s), g.show(s)))
}
}

checkAll("Coproduct[Show, Show, ?]", ContravariantTests[Coproduct[Show, Show, ?]].contravariant[Int, Int, Int])
checkAll("Contravariant[Coproduct[Show, Show, ?]]", SerializableTests.serializable(Contravariant[Coproduct[Show, Show, ?]]))

Expand Down
7 changes: 7 additions & 0 deletions tests/src/test/scala/cats/tests/FuncTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cats
package tests

import cats.data.{ Func, AppFunc }
import cats.functor.Contravariant
import Func.appFunc
import cats.laws.discipline._
import cats.laws.discipline.arbitrary._
Expand Down Expand Up @@ -37,6 +38,12 @@ class FuncTests extends CatsSuite {
checkAll("Functor[Func[Option, Int, ?]]", SerializableTests.serializable(Functor[Func[Option, Int, ?]]))
}

{
implicit val funcContravariant = Func.funcContravariant[Show, Int]
checkAll("Func[Show, Int, Int]", ContravariantTests[Func[Show, ?, Int]].contravariant[Int, Int, Int])
checkAll("Contravariant[Func[Show, ?, Int]]", SerializableTests.serializable(Contravariant[Func[Show, ?, Int]]))
}

{
implicit val appFuncApp = AppFunc.appFuncApplicative[Option, Int]
implicit val iso = CartesianTests.Isomorphisms.invariant[AppFunc[Option, Int, ?]]
Expand Down
4 changes: 4 additions & 0 deletions tests/src/test/scala/cats/tests/ProdTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cats
package tests

import cats.data.Prod
import cats.functor.Contravariant
import cats.laws.discipline._
import cats.laws.discipline.arbitrary._
import cats.laws.discipline.eq._
Expand All @@ -14,6 +15,9 @@ class ProdTests extends CatsSuite {
checkAll("Prod[Option, List, Int]", AlternativeTests[Lambda[X => Prod[Option, List, X]]].alternative[Int, Int, Int])
checkAll("Alternative[Prod[Option, List, Int]]", SerializableTests.serializable(Alternative[Lambda[X => Prod[Option, List, X]]]))

checkAll("Prod[Show, Order, Int]", ContravariantTests[Lambda[X => Prod[Show, Order, X]]].contravariant[Int, Int, Int])
checkAll("Contravariant[Prod[Show, Order, Int]]", SerializableTests.serializable(Contravariant[Lambda[X => Prod[Show, Order, X]]]))

{
implicit val monoidK = ListWrapper.monoidK
checkAll("Prod[ListWrapper, ListWrapper, ?]", MonoidKTests[Prod[ListWrapper, ListWrapper, ?]].monoidK[Int])
Expand Down
6 changes: 5 additions & 1 deletion tests/src/test/scala/cats/tests/WriterTTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package cats
package tests

import cats.data.{Writer, WriterT}
import cats.functor.Bifunctor
import cats.functor.{Bifunctor, Contravariant}
import cats.laws.discipline._
import cats.laws.discipline.eq._
import cats.laws.discipline.arbitrary._
Expand All @@ -20,6 +20,10 @@ class WriterTTests extends CatsSuite {

checkAll("WriterT[List, Int, Int]", OrderLaws[WriterT[List, Int, Int]].eqv)
checkAll("Eq[WriterT[List, Int, Int]]", SerializableTests.serializable(Eq[WriterT[List, Int, Int]]))

checkAll("WriterT[Show, Int, Int]", ContravariantTests[WriterT[Show, Int, ?]].contravariant[Int, Int, Int])
checkAll("Contravariant[WriterT[Show, Int, Int]]", SerializableTests.serializable(Contravariant[WriterT[Show, Int, ?]]))

// check that this resolves
Eq[Writer[Int, Int]]

Expand Down

0 comments on commit ab31ab1

Please sign in to comment.