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 ContravariantMonoidal #2034

Merged
merged 14 commits into from
Dec 1, 2017
Merged
10 changes: 10 additions & 0 deletions core/src/main/scala/cats/Composed.scala
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,16 @@ private[cats] trait ComposedContravariantCovariant[F[_], G[_]] extends Contravar
F.contramap(fga)(gb => G.map(gb)(f))
}

private[cats] trait ComposedApplicativeDivisible[F[_], G[_]] extends Divisible[λ[α => F[G[α]]]] { outer =>
def F: Applicative[F]
def G: Divisible[G]

override def unit[A]: F[G[A]] = F.pure(G.unit)

override def contramap2[A, B, C](fb: F[G[B]], fc: F[G[C]])(f: A => (B, C)): F[G[A]] =
F.ap2(F.pure((gb: G[B], gc: G[C]) => G.contramap2(gb, gc)(f)))(fb, fc)
}

private[cats] trait ComposedSemigroupal[F[_], G[_]] extends ContravariantSemigroupal[λ[α => F[G[α]]]] with ComposedContravariantCovariant[F, G] { outer =>
def F: ContravariantSemigroupal[F]
def G: Functor[G]
Expand Down
52 changes: 52 additions & 0 deletions core/src/main/scala/cats/Divisible.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package cats

import simulacrum.typeclass

/**
* Divisible functors
*
* Must obey the laws defined in cats.laws.DivisibleLaws.
*
* Based on ekmett's contravariant library:
* https://hackage.haskell.org/package/contravariant-1.4/docs/Data-Functor-Contravariant-Divisible.html
*/
@typeclass trait Divisible[F[_]] extends ContravariantSemigroupal[F] { self =>

/**
* `unit` produces an instance of `F` for any type `A`
*/
def unit[A]: F[A]

/**
* `contramap2`
*
* Given two values in the Divisible context, and a function producing a value of both types,
* yields an element of the domain of the function lifted into the context.
*/
def contramap2[A, B, C](fb: F[B], fc: F[C])(f: A => (B, C)): F[A]
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not provide a default implementation in terms of contramap and product? (like we do for covariant semigroupal: https://github.com/typelevel/cats/blob/master/core/src/main/scala/cats/Apply.scala#L44-L50 )

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, that makes sense, consistency is pretty valuable. I mostly went this approach based on the primary document.



// Technically, this is not correct, as the Applicative is composed with the Divisible, not the other way around
def composeApplicative[G[_]: Applicative]: Divisible[λ[α => G[F[α]]]] =
new ComposedApplicativeDivisible[G, F] {
val F = Applicative[G]
val G = self
}

/**
* Allows two instances to packaged into an instance over the product
*/
override def product[A, B](fa: F[A], fb: F[B]): F[(A, B)] =
contramap2(fa, fb)(identity)

/**
* Lifts a function into the Divisible contravariantly
*/
def liftD[A, B](f: A => B): F[B] => F[A] =
contramap2(unit, _: F[B])(((b: B) => (b, b)) compose f)


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

6 changes: 6 additions & 0 deletions core/src/main/scala/cats/data/Const.scala
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@ private[data] sealed abstract class ConstInstances extends ConstInstances0 {
fa.retag[B]
}

implicit def catsDataDivisibleForConst[D: Monoid]: Divisible[Const[D, ?]] = new Divisible[Const[D, ?]] {
override def unit[A] = Const.empty[D, A]
override def contramap2[A, B, C](fb: Const[D, B], fc: Const[D, C])(f: A => (B, C)): Const[D, A] =
fb.retag[A] combine fc.retag[A]
}

implicit def catsDataTraverseForConst[C]: Traverse[Const[C, ?]] = new Traverse[Const[C, ?]] {
def foldLeft[A, B](fa: Const[C, A], b: B)(f: (B, A) => B): B = b

Expand Down
16 changes: 15 additions & 1 deletion core/src/main/scala/cats/data/IdT.scala
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,15 @@ private[data] sealed trait IdTApplicative[F[_]] extends Applicative[IdT[F, ?]] w
def pure[A](a: A): IdT[F, A] = IdT.pure(a)
}

private[data] sealed trait IdTDivisible[F[_]] extends Divisible[IdT[F, ?]] {
implicit val F0: Divisible[F]

override def unit[A]: IdT[F, A] = IdT(F0.unit[A])

override def contramap2[A, B, C](fb: IdT[F, B], fc: IdT[F, C])(f: A => (B, C)): IdT[F, A] =
IdT(F0.contramap2(fb.value, fc.value)(f))
}

private[data] sealed trait IdTFlatMap[F[_]] extends FlatMap[IdT[F, ?]] with IdTApply[F] {
implicit val F0: FlatMap[F]

Expand Down Expand Up @@ -123,7 +132,12 @@ private[data] sealed trait IdTNonEmptyTraverse[F[_]] extends IdTTraverse[F] with
fa.reduceRightTo(f)(g)
}

private[data] sealed abstract class IdTInstances5 {
private[data] sealed abstract class IdTInstances6 {
implicit def catsDataDivisibleForIdT[F[_]](implicit F: Divisible[F]): Divisible[IdT[F, ?]] =
new IdTDivisible[F] { implicit val F0: Divisible[F] = F }
}

private[data] sealed abstract class IdTInstances5 extends IdTInstances6 {
implicit def catsDataFunctorForIdT[F[_]](implicit F: Functor[F]): Functor[IdT[F, ?]] =
new IdTFunctor[F] { implicit val F0: Functor[F] = F }
}
Expand Down
20 changes: 20 additions & 0 deletions core/src/main/scala/cats/data/IndexedStateT.scala
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,10 @@ private[data] sealed abstract class IndexedStateTInstances extends IndexedStateT
implicit def catsDataAlternativeForIndexedStateT[F[_], S](implicit FM: Monad[F],
FA: Alternative[F]): Alternative[IndexedStateT[F, S, S, ?]] with Monad[IndexedStateT[F, S, S, ?]] =
new IndexedStateTAlternative[F, S] { implicit def F = FM; implicit def G = FA }

implicit def catsDataDivisibleForIndexedStateT[F[_], S](implicit FD: Divisible[F],
FA: Applicative[F]): Divisible[IndexedStateT[F, S, S, ?]] =
new IndexedStateTDivisible[F, S] { implicit def F = FD; implicit def G = FA }
}

private[data] sealed abstract class IndexedStateTInstances1 extends IndexedStateTInstances2 {
Expand Down Expand Up @@ -362,6 +366,22 @@ private[data] sealed abstract class IndexedStateTSemigroupK[F[_], SA, SB] extend
IndexedStateT(s => G.combineK(x.run(s), y.run(s)))
}

private[data] sealed abstract class IndexedStateTDivisible[F[_], S] extends Divisible[IndexedStateT[F, S, S, ?]]{
implicit def F: Divisible[F]
implicit def G: Applicative[F]

override def unit[A]: IndexedStateT[F, S, S, A] =
IndexedStateT.applyF(G.pure((s: S) => F.unit[(S, A)]))

override def contramap2[A, B, C](fb: IndexedStateT[F, S, S, B], fc: IndexedStateT[F, S, S, C])(f: A => (B, C)): IndexedStateT[F, S, S, A] =
IndexedStateT.applyF(
G.pure((s: S) =>
F.contramap2(G.map(fb.runF)(_.apply(s)), G.map(fc.runF)(_.apply(s)))(
(tup: (S, A)) => f(tup._2) match {
case (b, c) => (G.pure((tup._1, b)), G.pure((tup._1, c)))
})))
}

private[data] sealed abstract class IndexedStateTAlternative[F[_], S] extends IndexedStateTMonad[F, S] with Alternative[IndexedStateT[F, S, S, ?]] {
def G: Alternative[F]

Expand Down
12 changes: 12 additions & 0 deletions core/src/main/scala/cats/data/Kleisli.scala
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,9 @@ private[data] sealed abstract class KleisliInstances1 extends KleisliInstances2
private[data] sealed abstract class KleisliInstances2 extends KleisliInstances3 {
implicit def catsDataAlternativeForKleisli[F[_], A](implicit F0: Alternative[F]): Alternative[Kleisli[F, A, ?]] =
new KleisliAlternative[F, A] { def F: Alternative[F] = F0 }

implicit def catsDataDivisibleForKleisli[F[_], A](implicit F0: Divisible[F]): Divisible[Kleisli[F, A, ?]] =
new KleisliDivisible[F, A] { def F: Divisible[F] = F0 }
}

private[data] sealed abstract class KleisliInstances3 extends KleisliInstances4 {
Expand Down Expand Up @@ -294,6 +297,15 @@ private[data] trait KleisliAlternative[F[_], A] extends Alternative[Kleisli[F, A
implicit def F: Alternative[F]
}

private[data] sealed trait KleisliDivisible[F[_], D] extends Divisible[Kleisli[F, D, ?]] {
implicit def F: Divisible[F]

override def unit[A]: Kleisli[F, D, A] = Kleisli(Function.const(F.unit[A]))

override def contramap2[A, B, C](fb: Kleisli[F, D, B], fc: Kleisli[F, D, C])(f: A => (B, C)): Kleisli[F, D, A] =
Kleisli(d => F.contramap2(fb.run(d), fc.run(d))(f))
}

private[data] trait KleisliMonadError[F[_], A, E] extends MonadError[Kleisli[F, A, ?], E] with KleisliApplicativeError[F, A, E] with KleisliMonad[F, A] {
def F: MonadError[F, E]
}
Expand Down
14 changes: 14 additions & 0 deletions core/src/main/scala/cats/data/Nested.scala
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ private[data] sealed abstract class NestedInstances1 extends NestedInstances2 {
new NestedFunctor[F, G] {
val FG: Functor[λ[α => F[G[α]]]] = Contravariant[F].compose[G]
}

implicit def catsDataDivisibleForApplicativeForNested[F[_]: Applicative, G[_]: Divisible]: Divisible[Nested[F, G, ?]] =
new NestedDivisible[F, G] {
val FG: Divisible[λ[α => F[G[α]]]] = Divisible[G].composeApplicative[F]
}
}

private[data] sealed abstract class NestedInstances2 extends NestedInstances3 {
Expand Down Expand Up @@ -263,3 +268,12 @@ private[data] trait NestedContravariant[F[_], G[_]] extends Contravariant[Nested
def contramap[A, B](fga: Nested[F, G, A])(f: B => A): Nested[F, G, B] =
Nested(FG.contramap(fga.value)(f))
}

private[data] trait NestedDivisible[F[_], G[_]] extends Divisible[Nested[F, G, ?]] {
def FG: Divisible[λ[α => F[G[α]]]]

def unit[A]: Nested[F, G, A] = Nested(FG.unit)

def contramap2[A, B, C](fb: Nested[F, G, B], fc: Nested[F, G, C])(f: A => (B, C)): Nested[F, G, A] =
Nested(FG.contramap2(fb.value, fc.value)(f))
}
13 changes: 13 additions & 0 deletions core/src/main/scala/cats/data/OptionT.scala
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,9 @@ private[data] sealed abstract class OptionTInstances0 extends OptionTInstances1
implicit def catsDataMonadErrorForOptionT[F[_], E](implicit F0: MonadError[F, E]): MonadError[OptionT[F, ?], E] =
new OptionTMonadError[F, E] { implicit val F = F0 }

implicit def catsDataDivisibleForOptionT[F[_]](implicit F0: Divisible[F]): Divisible[OptionT[F, ?]] =
new OptionTDivisible[F] { implicit val F = F0 }

implicit def catsDataSemigroupKForOptionT[F[_]](implicit F0: Monad[F]): SemigroupK[OptionT[F, ?]] =
new OptionTSemigroupK[F] { implicit val F = F0 }

Expand Down Expand Up @@ -281,6 +284,16 @@ private trait OptionTMonadError[F[_], E] extends MonadError[OptionT[F, ?], E] wi
OptionT(F.handleErrorWith(fa.value)(f(_).value))
}

private trait OptionTDivisible[F[_]] extends Divisible[OptionT[F, ?]] {
def F: Divisible[F]

override def unit[A]: OptionT[F, A] = OptionT (F.unit)

// Really this wants to be written in terms of arrow :/
override def contramap2[A, B, C](fb: OptionT[F, B], fc: OptionT[F, C])(f: A => (B, C)): OptionT[F, A] =
OptionT(F.contramap2(fb.value, fc.value)(x => (x.map(f andThen (_._1)), x.map(f andThen (_._2)))))
}

private[data] trait OptionTFoldable[F[_]] extends Foldable[OptionT[F, ?]] {
implicit def F: Foldable[F]

Expand Down
13 changes: 13 additions & 0 deletions core/src/main/scala/cats/data/Tuple2K.scala
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ private[data] sealed abstract class Tuple2KInstances extends Tuple2KInstances0 {
def F: Contravariant[F] = FC
def G: Contravariant[G] = GC
}
implicit def catsDataDivisibleForTuple2k[F[_], G[_]](implicit FD: Divisible[F], GD: Divisible[G]): Divisible[λ[α => Tuple2K[F, G, α]]] =
new Tuple2KDivisible[F, G] {
def F: Divisible[F] = FD
def G: Divisible[G] = GD
}
}

private[data] sealed abstract class Tuple2KInstances0 extends Tuple2KInstances1 {
Expand Down Expand Up @@ -123,6 +128,14 @@ private[data] sealed trait Tuple2KContravariant[F[_], G[_]] extends Contravarian
def contramap[A, B](fa: Tuple2K[F, G, A])(f: B => A): Tuple2K[F, G, B] = Tuple2K(F.contramap(fa.first)(f), G.contramap(fa.second)(f))
}

private[data] sealed trait Tuple2KDivisible[F[_], G[_]] extends Divisible[λ[α => Tuple2K[F, G, α]]] {
def F: Divisible[F]
def G: Divisible[G]
def unit[A]: Tuple2K[F, G, A] = Tuple2K(F.unit, G.unit)
def contramap2[A, B, C](fb: Tuple2K[F, G, B], fc: Tuple2K[F, G, C])(f: A => (B, C)): Tuple2K[F, G, A] =
Tuple2K(F.contramap2(fb.first, fc.first)(f), G.contramap2(fb.second, fc.second)(f))
}

private[data] sealed trait Tuple2KApply[F[_], G[_]] extends Apply[λ[α => Tuple2K[F, G, α]]] with Tuple2KFunctor[F, G] {
def F: Apply[F]
def G: Apply[G]
Expand Down
16 changes: 16 additions & 0 deletions core/src/main/scala/cats/data/WriterT.scala
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,11 @@ private[data] sealed abstract class WriterTInstances6 extends WriterTInstances7
implicit val F0: Alternative[F] = F
implicit val L0: Monoid[L] = L
}

implicit def catsDataDivisibleForWriterT[F[_], L](implicit F: Divisible[F]): Divisible[WriterT[F, L, ?]] =
new WriterTDivisible[F, L] {
implicit val F0: Divisible[F] = F
}
}

private[data] sealed abstract class WriterTInstances7 extends WriterTInstances8 {
Expand Down Expand Up @@ -353,6 +358,17 @@ private[data] sealed trait WriterTAlternative[F[_], L] extends Alternative[Write
override implicit def F0: Alternative[F]
}

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

override def unit[A]: WriterT[F, L, A] = WriterT(F0.unit[(L, A)])

override def contramap2[A, B, C](fb: WriterT[F, L, B], fc: WriterT[F, L, C])(f: A => (B, C)): WriterT[F, L, A] =
WriterT(F0.contramap2(fb.run, fc.run)((tup: (L, A)) => f(tup._2) match {
case (b, c) => ((tup._1, b), (tup._1, c))
}))
}

private[data] sealed trait WriterTSemigroup[F[_], L, A] extends Semigroup[WriterT[F, L, A]] {
implicit def F0: Semigroup[F[(L, A)]]

Expand Down
24 changes: 19 additions & 5 deletions core/src/main/scala/cats/instances/eq.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,25 @@ package cats
package instances

trait EqInstances {
implicit val catsContravariantSemigroupalForEq: ContravariantSemigroupal[Eq] =
new ContravariantSemigroupal[Eq] {
def contramap[A, B](fa: Eq[A])(fn: B => A): Eq[B] = Eq.by[B, A](fn)(fa)
implicit val catsDivisibleForEq: Divisible[Eq] =
new Divisible[Eq] {
/**
* Defaults to the trivial equivalence relation
* contracting the type to a point
*/
def unit[A]: Eq[A] = Eq.allEqual

def product[A, B](fa: Eq[A], fb: Eq[B]): Eq[(A, B)] =
Eq.instance { (left, right) => fa.eqv(left._1, right._1) && fb.eqv(left._2, right._2) }
/** Derive an `Eq` for `B` given an `Eq[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 contramap2[A, B, C](fb: Eq[B], fc: Eq[C])(f: A => (B, C)): Eq[A] =
Eq.instance { (l, r) =>
Copy link
Contributor

Choose a reason for hiding this comment

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

If I am not mistaken, the previous version allocates at least one tuple less (although it is admittedly uglier :-))

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I guess that's true, I sort of forgot that aspect of things. 👍

(f(l), f(r)) match {
case (derivedL, derivedR) =>
fb.eqv(derivedL._1, derivedR._1) &&
fc.eqv(derivedL._2, derivedR._2)
}
}
}
}
31 changes: 21 additions & 10 deletions core/src/main/scala/cats/instances/equiv.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,28 @@ package cats
package instances

trait EquivInstances {
implicit val catsContravariantSemigroupalEquiv: ContravariantSemigroupal[Equiv] =
new ContravariantSemigroupal[Equiv] {
def contramap[A, B](fa: Equiv[A])(f: B => A): Equiv[B] =
new Equiv[B] {
def equiv(x: B, y: B): Boolean = fa.equiv(f(x), f(y))
}
implicit val catsDivisibleForEquiv: Divisible[Equiv] =
new Divisible[Equiv] {
/**
* Defaults to trivially contracting the type
* to a point
*/
def unit[A]: Equiv[A] = new Equiv[A] {
def equiv(x: A, y: A): Boolean = true
}

def product[A, B](fa: Equiv[A], fb: Equiv[B]): Equiv[(A, B)] =
new Equiv[(A, B)] {
def equiv(x: (A, B), y: (A, B)): Boolean =
fa.equiv(x._1, y._1) && fb.equiv(x._2, y._2)
Copy link
Contributor

Choose a reason for hiding this comment

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

Same comment re allocations

/** Derive an `Equiv` for `B` given an `Equiv[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 contramap2[A, B, C](fb: Equiv[B], fc: Equiv[C])(f: A => (B, C)): Equiv[A] =
new Equiv[A] {
def equiv(l: A, r: A): Boolean =
(f(l), f(r)) match {
case (derivedL, derivedR) =>
fb.equiv(derivedL._1, derivedR._1) &&
fc.equiv(derivedL._2, derivedR._2)
}
}
}
}
9 changes: 9 additions & 0 deletions core/src/main/scala/cats/instances/function.scala
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,15 @@ private[instances] sealed trait Function1Instances {
fa.compose(f)
}

implicit def catsStdDivisibleForFunction1[R: Monoid]: Divisible[? => R] =
new Divisible[? => R] {
def unit[A]: A => R = Function.const(Monoid[R].empty)
def contramap2[A, B, C](fb: B => R, fc: C => R)(f: A => (B, C)): A => R =
a => f(a) match {
case (b, c) => Monoid[R].combine(fb(b), fc(c))
}
}

implicit def catsStdMonadForFunction1[T1]: Monad[T1 => ?] =
new Monad[T1 => ?] {
def pure[R](r: R): T1 => R = _ => r
Expand Down
Loading