Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding more contravariant instances #929

Merged
merged 1 commit into from
Jun 14, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 catsDataSemigroupKForCokleisli[F[_]](implicit ev: CoflatMap[F]): SemigroupK[λ[α => Cokleisli[F, α, α]]] =
new CokleisliSemigroupK[F] { def F: CoflatMap[F] = ev }

implicit def catsDataContravariantForCokleisli[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 catsDataContravariantForFunc[F[_], C](implicit FC: Contravariant[F]): Contravariant[λ[α => Func[F, α, C]]] =
new FuncContravariant[F, C] {
def F: Contravariant[F] = FC
}
}

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

sealed trait FuncContravariant[F[_], C] extends Contravariant[λ[α => Func[F, α, 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[λ[α => Func[F, C, α]]] 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 @@ -55,6 +57,10 @@ private[data] sealed abstract class ProdInstances4 {
def F: Functor[F] = FF
def G: Functor[G] = GG
}
implicit def catsDataContravariantForProd[F[_], G[_]](implicit FC: Contravariant[F], GC: Contravariant[G]): Contravariant[λ[α => Prod[F, G, α]]] = new ProdContravariant[F, G] {
def F: Contravariant[F] = FC
def G: Contravariant[G] = GC
}
}

sealed trait ProdFunctor[F[_], G[_]] extends Functor[λ[α => Prod[F, G, α]]] {
Expand All @@ -63,6 +69,12 @@ sealed trait ProdFunctor[F[_], G[_]] extends Functor[λ[α => Prod[F, G, α]]] {
override 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[λ[α => Prod[F, G, α]]] {
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[λ[α => Prod[F, G, α]]] with ProdFunctor[F, G] {
def F: Apply[F]
def G: Apply[G]
Expand Down
18 changes: 17 additions & 1 deletion core/src/main/scala/cats/data/WriterT.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package cats
package data

import cats.kernel.std.tuple._
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 @@ -22,6 +22,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 @@ -176,6 +181,10 @@ private[data] sealed abstract class WriterTInstances7 {
new WriterTCoflatMap[F, L] {
implicit val F0: Functor[F] = F
}

implicit def catsDataContravariantForWriterT[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 @@ -185,6 +194,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
20 changes: 20 additions & 0 deletions core/src/main/scala/cats/functor/Contravariant.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,23 @@ import simulacrum.typeclass
val G = Functor[G]
}
}

object Contravariant {
implicit val catsFunctorContravariantForPartialOrder: Contravariant[PartialOrder] =
new Contravariant[PartialOrder] {
/** Derive a `PartialOrder` for `B` given a `PartialOrder[A]` and a function `B => A`.
*
* Note: resulting instances are law-abiding only when the functions used are injective (represent a one-to-one mapping)
*/
def contramap[A, B](fa: PartialOrder[A])(f: B => A): PartialOrder[B] = fa.on(f)
}

implicit val catsFunctorContravariantForOrder: Contravariant[Order] =
new Contravariant[Order] {
/** Derive an `Order` for `B` given an `Order[A]` and a function `B => A`.
*
* Note: resulting instances are law-abiding only when the functions used are injective (represent a one-to-one mapping)
*/
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 @@ -77,6 +77,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 @@ -24,6 +24,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]] = {
val xyz = function1Eq[A, String]
Expand Down
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 @@ -6,7 +6,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 @@ -31,16 +31,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.catsDataContravariantForFunc[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
15 changes: 15 additions & 0 deletions tests/src/test/scala/cats/tests/KernelContravariantTests.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 KernelContravariantTests 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: 5 additions & 0 deletions tests/src/test/scala/cats/tests/ProdTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ 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._

class ProdTests extends CatsSuite {
implicit val iso = CartesianTests.Isomorphisms.invariant[Prod[Option, List, ?]]
Expand All @@ -13,6 +15,9 @@ class ProdTests extends CatsSuite {
checkAll("Prod[Option, List, Int]", AlternativeTests[λ[α => Prod[Option, List, α]]].alternative[Int, Int, Int])
checkAll("Alternative[Prod[Option, List, Int]]", SerializableTests.serializable(Alternative[λ[α => Prod[Option, List, α]]]))

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

{
implicit val monoidK = ListWrapper.monoidK
checkAll("Prod[ListWrapper, ListWrapper, ?]", MonoidKTests[Prod[ListWrapper, ListWrapper, ?]].monoidK[Int])
Expand Down
7 changes: 6 additions & 1 deletion tests/src/test/scala/cats/tests/WriterTTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ 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.arbitrary._
import cats.laws.discipline.eq._

import cats.kernel.laws.OrderLaws

Expand All @@ -19,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