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

Add some Contravariant instances #590

Closed
wants to merge 3 commits into from
Closed
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: 7 additions & 0 deletions core/src/main/scala/cats/data/Const.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package cats
package data

import cats.functor.Contravariant

/**
* [[Const]] is a phantom type, it does not contain a value of its second type parameter `B`
* [[Const]] can be seen as a type level version of `Function.const[A, B]: A => B => A`
Expand Down Expand Up @@ -92,4 +94,9 @@ sealed abstract class ConstInstances1 {
def map[A, B](fa: Const[C, A])(f: A => B): Const[C, B] =
fa.retag[B]
}

implicit def constContravariant[C]: Contravariant[Const[C, ?]] = new Contravariant[Const[C, ?]] {
def contramap[A, B](fa: Const[C, A])(f: B => A): Const[C, B] =
fa.retag[B]
}
}
17 changes: 15 additions & 2 deletions core/src/main/scala/cats/data/Kleisli.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, Choice, Split}
import cats.functor.Strong
import cats.functor.{Contravariant, Strong}

/**
* Represents a function `A => F[B]`.
Expand All @@ -21,6 +21,9 @@ final case class Kleisli[F[_], A, B](run: A => F[B]) { self =>
def map[C](f: B => C)(implicit F: Functor[F]): Kleisli[F, A, C] =
Kleisli(a => F.map(run(a))(f))

def contramap[C](f: C => B)(implicit F: Contravariant[F]): Kleisli[F, A, C] =
Kleisli(a => F.contramap(run(a))(f))

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the contramap that I would expect for Kleisli would be the local method that already exists on it. I think in practice the F in a Kleisli is often going to be covariant (not contravariant) in nature (Future, Task, Xor, etc), so I don't know how often this would be helpful. If you have some good examples of how you might use this, I'd definitely be interested in them.

Otherwise, should we get rid of this one and use local for the Contravariant instance?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that does seem more useful. Is there a way to keep both the instances?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For simplicity, I'd be inclined to just keep the one until someone runs into a compelling use-case for the other.

def mapF[N[_], C](f: F[B] => N[C]): Kleisli[N, A, C] =
Kleisli(run andThen f)

Expand Down Expand Up @@ -125,6 +128,9 @@ sealed abstract class KleisliInstances0 extends KleisliInstances1 {
implicit def kleisliStrong[F[_]](implicit ev: Functor[F]): Strong[Kleisli[F, ?, ?]] =
new KleisliStrong[F] { def F: Functor[F] = ev }

implicit def kleisliContravariant[F[_], A](implicit ev: Contravariant[F]): Contravariant[Kleisli[F, A, ?]] =
new KleisliContravariant[F, A] { def F: Contravariant[F] = ev }

implicit def kleisliFlatMap[F[_]: FlatMap, A]: FlatMap[Kleisli[F, A, ?]] = new FlatMap[Kleisli[F, A, ?]] {
def flatMap[B, C](fa: Kleisli[F, A, B])(f: B => Kleisli[F, A, C]): Kleisli[F, A, C] =
fa.flatMap(f)
Expand Down Expand Up @@ -212,10 +218,17 @@ private trait KleisliStrong[F[_]] extends Strong[Kleisli[F, ?, ?]] {
fa.second[C]
}

private trait KleisliContravariant[F[_], A] extends Contravariant[Kleisli[F, A, ?]] {
implicit def F: Contravariant[F]

override def contramap[B, C](fa: Kleisli[F, A, B])(f: C => B): Kleisli[F, A, C] =
fa.contramap(f)
}

private trait KleisliSemigroup[F[_], A, B] extends Semigroup[Kleisli[F, A, B]] {
implicit def FB: Semigroup[F[B]]

override def combine(a: Kleisli[F, A, B], b: Kleisli[F, A, B]): Kleisli[F, A, B] =
override def combine(a: Kleisli[F, A, B], b: Kleisli[F, A, B]): Kleisli[F, A, B] =
Kleisli[F, A, B](x => FB.combine(a.run(x), b.run(x)))
}

Expand Down
11 changes: 11 additions & 0 deletions core/src/main/scala/cats/data/OptionT.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package cats
package data

import functor.Contravariant

/**
* `OptionT[F[_], A` is a light wrapper on an `F[Option[A]]` with some
* convenient methods for working with this nested structure.
Expand All @@ -25,6 +27,9 @@ final case class OptionT[F[_], A](value: F[Option[A]]) {
def map[B](f: A => B)(implicit F: Functor[F]): OptionT[F, B] =
OptionT(F.map(value)(_.map(f)))

def contramap[B](f: B => A)(implicit F: Contravariant[F]): OptionT[F, B] =
OptionT(F.contramap(value)(_.map(f)))

def flatMap[B](f: A => OptionT[F, B])(implicit F: Monad[F]): OptionT[F, B] =
OptionT(
F.flatMap(value){
Expand Down Expand Up @@ -138,4 +143,10 @@ trait OptionTInstances extends OptionTInstances1 {
}
implicit def optionTEq[F[_], A](implicit FA: Eq[F[Option[A]]]): Eq[OptionT[F, A]] =
FA.on(_.value)

implicit def optionTContravariant[F[_]: Contravariant]: Contravariant[OptionT[F, ?]] =
new Contravariant[OptionT[F, ?]] {
def contramap[A, B](fa: OptionT[F, A])(f: B => A): OptionT[F, B] =
fa.contramap(f)
}
}
9 changes: 8 additions & 1 deletion tests/src/test/scala/cats/tests/ConstTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ package tests
import algebra.laws.{GroupLaws, OrderLaws}

import cats.data.{Const, NonEmptyList}
import cats.laws.discipline.{ApplyTests, ApplicativeTests, SerializableTests, TraverseTests}
import cats.functor.Contravariant
import cats.laws.discipline.{ApplyTests, ApplicativeTests, ContravariantTests, SerializableTests, TraverseTests}
import cats.laws.discipline.arbitrary.{constArbitrary, oneAndArbitrary}

class ConstTests extends CatsSuite {
Expand All @@ -31,4 +32,10 @@ class ConstTests extends CatsSuite {
checkAll("Const[Map[Int, Int], String]", OrderLaws[Const[Map[Int, Int], String]].eqv)
checkAll("PartialOrder[Const[Set[Int], String]]", OrderLaws[Const[Set[Int], String]].partialOrder)
checkAll("Order[Const[Int, String]]", OrderLaws[Const[Int, String]].order)

{
implicit val constContravariant = Const.constContravariant[Int]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this necessary to get the law-checking to compile? It seems like we shouldn't have to do this.

checkAll("Const[Int, Int]", ContravariantTests[Const[Int, ?]].contravariant[Int, String, Boolean])
checkAll("Contravariant[Const[Int, ?]]", SerializableTests.serializable(Contravariant[Const[Int, ?]]))
}
}
10 changes: 8 additions & 2 deletions tests/src/test/scala/cats/tests/KleisliTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package cats
package tests

import cats.arrow.{Arrow, Choice, Split}
import cats.data.Kleisli
import cats.functor.Strong
import cats.data.{Const, Kleisli}
import cats.functor.{Contravariant, Strong}
import cats.laws.discipline._
import cats.laws.discipline.arbitrary._
import cats.laws.discipline.eq._
Expand Down Expand Up @@ -45,6 +45,12 @@ class KleisliTests extends CatsSuite {
checkAll("Strong[Kleisli[Option, ?, ?]]", SerializableTests.serializable(Strong[Kleisli[Option, ?, ?]]))
}

{
implicit val kleisliContravariant = Kleisli.kleisliContravariant[Const[Int, ?], Int]
// checkAll("Kleisli[Const[Int, ?], Int, ?]", ContravariantTests[Kleisli[Const[Int, ?], Int, ?]].contravariant[Int, Int, Int])
checkAll("Contravariant[Kleisli[Const[Int, ?], Int, ?]]", SerializableTests.serializable(Contravariant[Kleisli[Const[Int, ?], Int, ?]]))
}

{
implicit val kleisliFlatMap = Kleisli.kleisliFlatMap[Option, Int]
checkAll("Kleisli[Option, Int, Int]", FlatMapTests[Kleisli[Option, Int, ?]].flatMap[Int, Int, Int])
Expand Down
11 changes: 9 additions & 2 deletions tests/src/test/scala/cats/tests/OptionTTests.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package cats.tests

import cats.{Applicative, Id, Monad}
import cats.data.{OptionT, Validated, Xor}
import cats.laws.discipline.{ApplicativeTests, FunctorTests, MonadCombineTests, SerializableTests}
import cats.data.{Const, OptionT, Validated, Xor}
import cats.functor.Contravariant
import cats.laws.discipline.{ApplicativeTests, ContravariantTests, FunctorTests, MonadCombineTests, SerializableTests}
import cats.laws.discipline.arbitrary._
import org.scalacheck.{Arbitrary, Gen}

Expand Down Expand Up @@ -118,4 +119,10 @@ class OptionTTests extends CatsSuite {
implicit val F = ListWrapper.functor
checkAll("Functor[OptionT[ListWrapper, ?]]", FunctorTests[OptionT[ListWrapper, ?]].functor[Int, Int, Int])
}

{
implicit val optionTContravariant = OptionT.optionTContravariant[Const[Int, ?]]
// checkAll("OptionT[Const[Int, ?], Int]", ContravariantTests[OptionT[Const[Int, ?], ?]].contravariant[Int, Int, Int])
checkAll("ContravariantOptionT[Const[Int, ?], ?]]", SerializableTests.serializable(Contravariant[OptionT[Const[Int, ?], ?]]))
}
}