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
Show file tree
Hide file tree
Changes from 7 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
31 changes: 31 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,37 @@ def mimaSettings(moduleName: String) = Seq(
exclude[MissingTypesProblem]("cats.data.OneAndLowPriority3"),
exclude[MissingTypesProblem]("cats.data.OneAndLowPriority2"),
exclude[MissingTypesProblem]("cats.data.OneAndLowPriority1"),
exclude[DirectMissingMethodProblem]("cats.implicits.catsContravariantSemigroupalForEq"),
exclude[DirectMissingMethodProblem]("cats.implicits.catsContravariantSemigroupalForOrder"),
exclude[DirectMissingMethodProblem]("cats.implicits.catsContravariantSemigroupalForOrdering"),
exclude[DirectMissingMethodProblem]("cats.implicits.catsContravariantSemigroupalEquiv"),
exclude[ReversedMissingMethodProblem]("cats.ContravariantSemigroupal.contramap2"),
exclude[ReversedMissingMethodProblem]("cats.ContravariantSemigroupal#Ops.contramap2"),
exclude[InheritedNewAbstractMethodProblem]("cats.ContravariantMonoidal#ToContravariantMonoidalOps.toContravariantMonoidalOps"),
exclude[InheritedNewAbstractMethodProblem]("cats.ContravariantSemigroupal#ToContravariantSemigroupalOps.toContravariantSemigroupalOps"),
exclude[DirectMissingMethodProblem]("cats.instances.package=uiv.catsContravariantSemigroupalEquiv"),
exclude[DirectMissingMethodProblem]("cats.instances.OrderingInstances.catsContravariantSemigroupalForOrdering"),
exclude[ReversedMissingMethodProblem]("cats.instances.OrderingInstances.cats$instances$OrderingInstances$_setter_$catsContravariantMonoidalForOrdering_="),
exclude[ReversedMissingMethodProblem]("cats.instances.OrderingInstances.catsContravariantMonoidalForOrdering"),
exclude[DirectMissingMethodProblem]("cats.instances.OrderInstances.catsContravariantSemigroupalForOrder"),
exclude[ReversedMissingMethodProblem]("cats.instances.OrderInstances.catsContravariantMonoidalForOrder"),
exclude[ReversedMissingMethodProblem]("cats.instances.OrderInstances.cats$instances$OrderInstances$_setter_$catsContravariantMonoidalForOrder_="),
exclude[DirectMissingMethodProblem]("cats.instances.package#ordering.catsContravariantSemigroupalForOrdering"),
exclude[DirectMissingMethodProblem]("cats.instances.package#all.catsContravariantSemigroupalForEq"),
exclude[DirectMissingMethodProblem]("cats.instances.package#all.catsContravariantSemigroupalForOrder"),
exclude[DirectMissingMethodProblem]("cats.instances.package#all.catsContravariantSemigroupalForOrdering"),
exclude[DirectMissingMethodProblem]("cats.instances.package#all.catsContravariantSemigroupalEquiv"),
exclude[DirectMissingMethodProblem]("cats.instances.EquivInstances.catsContravariantSemigroupalEquiv"),
exclude[ReversedMissingMethodProblem]("cats.instances.EquivInstances.catsContravariantMonoidalForEquiv"),
exclude[ReversedMissingMethodProblem]("cats.instances.EquivInstances.cats$instances$EquivInstances$_setter_$catsContravariantMonoidalForEquiv_="),
exclude[DirectMissingMethodProblem]("cats.instances.package=.catsContravariantSemigroupalForEq"),
exclude[DirectMissingMethodProblem]("cats.instances.package#order.catsContravariantSemigroupalForOrder"),
exclude[ReversedMissingMethodProblem]("cats.instances.Function1Instances.catsStdContravariantMonoidalForFunction1"),
exclude[DirectMissingMethodProblem]("cats.instances.EqInstances.catsContravariantSemigroupalForEq"),
exclude[ReversedMissingMethodProblem]("cats.instances.EqInstances.catsContravariantMonoidalForEq"),
exclude[ReversedMissingMethodProblem]("cats.instances.EqInstances.cats$instances$EqInstances$_setter_$catsContravariantMonoidalForEq_="),
exclude[InheritedNewAbstractMethodProblem]("cats.syntax.ContravariantSemigroupalSyntax.catsSyntaxContravariantSemigroupal"),
exclude[InheritedNewAbstractMethodProblem]("cats.syntax.ContravariantMonoidalSyntax.catsSyntaxContravariantMonoidal"),
exclude[DirectMissingMethodProblem]("cats.data.OneAndLowPriority3.catsDataNonEmptyTraverseForOneAnd"),
exclude[DirectMissingMethodProblem]("cats.data.OneAndLowPriority2.catsDataTraverseForOneAnd"),
exclude[ReversedMissingMethodProblem]("cats.instances.ParallelInstances.catsStdNonEmptyParallelForZipVector"),
Expand Down
13 changes: 13 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,19 @@ private[cats] trait ComposedContravariantCovariant[F[_], G[_]] extends Contravar
F.contramap(fga)(gb => G.map(gb)(f))
}

private[cats] trait ComposedApplicativeContravariantMonoidal[F[_], G[_]] extends ContravariantMonoidal[λ[α => F[G[α]]]] { outer =>
Copy link
Contributor

@julienrf julienrf Nov 23, 2017

Choose a reason for hiding this comment

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

I wouldn’t repeat Monoidal here because Applicative already implies Monoidal since they are equivalent (see also this detailed explanation: https://stackoverflow.com/questions/23316255/lax-monoidal-functors-with-a-different-monoidal-structure).

Edit: sorry I was wrong. This trait composes a covariant applicative with a contravariant one, so the naming is correct, you can ignore my comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The SO was a good read, anyway, it clarified some of the points on different Monoidal structures for me, so thank you.

def F: Applicative[F]
def G: ContravariantMonoidal[G]

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

override def contramap[A, B](fa: F[G[A]])(f: B => A): F[G[B]] =
F.map(fa)(G.contramap(_)(f))

override def product[A, B](fa: F[G[A]], fb: F[G[B]]): F[G[(A, B)]] =
F.map2(fa, fb)(G.product(_, _))
}

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
33 changes: 33 additions & 0 deletions core/src/main/scala/cats/ContravariantMonoidal.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package cats

import simulacrum.typeclass

/**
* [[ContravariantMonoidal]] functors are functors that supply
* a unit along the diagonal map for the `contramap2` operation.
*
* Must obey the laws defined in cats.laws.ContravariantMonoidalLaws.
*
* Based on ekmett's contravariant library:
* https://hackage.haskell.org/package/contravariant-1.4/docs/Data-Functor-Contravariant-Divisible.html
*/
@typeclass trait ContravariantMonoidal[F[_]] extends ContravariantSemigroupal[F] { self =>
/**
* `unit` produces an instance of `F` for any type `A`
* that is trivial with respect to `contramap2` along
* the diagonal
*/
def unit[A]: F[A]

def liftContravariant[A, B](f: A => B): F[B] => 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.

This can actually be moved to Contravariant, no? (as it's just a curried contramap)

ContravariantMonoidal.contramap2(unit[B], _: F[B])(((b: B) => (b, b)) compose f)(self, self)

// Technically, this is not correct, as the Applicative is composed with the ContravariantMonoidal, not the other way around
Copy link
Contributor

Choose a reason for hiding this comment

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

Would you mind elaborate a bit more (or point to a more detailed discussion/explanation) here?

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, I can also just change the name and move this possibly. The point here is mostly that I didn't want to add anything to Applicative about ContravariantMonoidal since this might live in cats.more, so I ended up having this composition live here in ContravariantMonoidal even though the composition is Applicative compose ContravariantMonoidal, so the name and the signature are a little out of sync with what I saw in the rest of the repository.

Copy link
Contributor

Choose a reason for hiding this comment

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

ah, I propose we rename and move it to Applicative

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Got it, will do. Thanks.

def composeApplicative[G[_]: Applicative]: ContravariantMonoidal[λ[α => G[F[α]]]] =
new ComposedApplicativeContravariantMonoidal[G, F] {
val F = Applicative[G]
val G = self
}
}
object ContravariantMonoidal extends SemigroupalArityFunctions {
}
2 changes: 2 additions & 0 deletions core/src/main/scala/cats/ContravariantSemigroupal.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ import simulacrum.typeclass
def G = Functor[G]
}
}
object ContravariantSemigroupal extends SemigroupalArityFunctions {
Copy link
Member

Choose a reason for hiding this comment

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

nitpick, but can we get rid of these curly braces? :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hah, yeah, for sure.

}
8 changes: 8 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,14 @@ private[data] sealed abstract class ConstInstances extends ConstInstances0 {
fa.retag[B]
}

implicit def catsDataContravariantMonoidalForConst[D: Monoid]: ContravariantMonoidal[Const[D, ?]] = new ContravariantMonoidal[Const[D, ?]] {
override def unit[A] = Const.empty[D, A]
override def contramap[A, B](fa: Const[D, A])(f: B => A): Const[D, B] =
fa.retag[B]
override def product[A, B](fa: Const[D, A], fb: Const[D, B]): Const[D, (A, B)] =
fa.retag[(A, B)] combine fb.retag[(A, B)]
}

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
19 changes: 18 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,18 @@ 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 IdTContravariantMonoidal[F[_]] extends ContravariantMonoidal[IdT[F, ?]] {
implicit val F0: ContravariantMonoidal[F]

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

override def contramap[A, B](fa: IdT[F, A])(f: B => A): IdT[F, B] =
IdT(F0.contramap(fa.value)(f))

override def product[A, B](fa: IdT[F, A], fb: IdT[F, B]): IdT[F, (A, B)] =
IdT(F0.product(fa.value, fb.value))
}

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

Expand Down Expand Up @@ -123,7 +135,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 catsDataContravariantMonoidalForIdT[F[_]](implicit F: ContravariantMonoidal[F]): ContravariantMonoidal[IdT[F, ?]] =
new IdTContravariantMonoidal[F] { implicit val F0: ContravariantMonoidal[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
26 changes: 26 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 catsDataContravariantMonoidalForIndexedStateT[F[_], S](implicit FD: ContravariantMonoidal[F],
FA: Applicative[F]): ContravariantMonoidal[IndexedStateT[F, S, S, ?]] =
new IndexedStateTContravariantMonoidal[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,28 @@ 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 IndexedStateTContravariantMonoidal[F[_], S] extends ContravariantMonoidal[IndexedStateT[F, S, S, ?]]{
implicit def F: ContravariantMonoidal[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 contramap[A, B](fa: IndexedStateT[F, S, S, A])(f: B => A): IndexedStateT[F, S, S, B] =
contramap2(fa, unit)(((a: A) => (a, a)) compose f)

override def product[A, B](fa: IndexedStateT[F, S, S, A], fb: IndexedStateT[F, S, S, B]): IndexedStateT[F, S, S, (A, B)] =
contramap2(fa, fb)(identity)

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) =>
ContravariantMonoidal.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)))
})(G, F)))
}

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

Expand Down
15 changes: 15 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 catsDataContravariantMonoidalForKleisli[F[_], A](implicit F0: ContravariantMonoidal[F]): ContravariantMonoidal[Kleisli[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.

The convention in cats is that instances that are more specific should be at higher priority. I.E. instance of ContravariantMonoidal should be lower in the inheritance chain than Contravariant and ContravriantSemigroupal and Semigroupal.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Gotcha, will fix.

new KleisliContravariantMonoidal[F, A] { def F: ContravariantMonoidal[F] = F0 }
}

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

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

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

override def contramap[A, B](fa: Kleisli[F, D, A])(f: B => A): Kleisli[F, D, B] =
Kleisli(d => F.contramap(fa.run(d))(f))

override def product[A, B](fa: Kleisli[F, D, A], fb: Kleisli[F, D, B]): Kleisli[F, D, (A, B)] =
Kleisli(d => F.product(fa.run(d), fb.run(d)))
}

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
17 changes: 17 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 catsDataContravariantMonoidalForApplicativeForNested[F[_]: Applicative, G[_]: ContravariantMonoidal]: ContravariantMonoidal[Nested[F, G, ?]] =
new NestedContravariantMonoidal[F, G] {
val FG: ContravariantMonoidal[λ[α => F[G[α]]]] = ContravariantMonoidal[G].composeApplicative[F]
}
}

private[data] sealed abstract class NestedInstances2 extends NestedInstances3 {
Expand Down Expand Up @@ -263,3 +268,15 @@ 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 NestedContravariantMonoidal[F[_], G[_]] extends ContravariantMonoidal[Nested[F, G, ?]] {
def FG: ContravariantMonoidal[λ[α => F[G[α]]]]

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

def contramap[A, B](fa: Nested[F, G, A])(f: B => A): Nested[F, G, B] =
Nested(FG.contramap(fa.value)(f))

def product[A, B](fa: Nested[F, G, A], fb: Nested[F, G, B]): Nested[F, G, (A, B)] =
Nested(FG.product(fa.value, fb.value))
}
20 changes: 20 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 catsDataContravariantMonoidalForOptionT[F[_]](implicit F0: ContravariantMonoidal[F]): ContravariantMonoidal[OptionT[F, ?]] =
new OptionTContravariantMonoidal[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,23 @@ private trait OptionTMonadError[F[_], E] extends MonadError[OptionT[F, ?], E] wi
OptionT(F.handleErrorWith(fa.value)(f(_).value))
}

private trait OptionTContravariantMonoidal[F[_]] extends ContravariantMonoidal[OptionT[F, ?]] {
def F: ContravariantMonoidal[F]

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

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

override def product[A, B](fa: OptionT[F, A], fb: OptionT[F, B]): OptionT[F, (A, B)] =
OptionT(F.contramap(F.product(fa.value, fb.value))(
(t: Option[(A, B)]) => t match {
case Some((x, y)) => (Some(x), Some(y))
case None => (None, None)
}
))
}

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

Expand Down
15 changes: 15 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 catsDataContravariantMonoidalForTuple2k[F[_], G[_]](implicit FD: ContravariantMonoidal[F], GD: ContravariantMonoidal[G]): ContravariantMonoidal[λ[α => Tuple2K[F, G, α]]] =
new Tuple2KContravariantMonoidal[F, G] {
def F: ContravariantMonoidal[F] = FD
def G: ContravariantMonoidal[G] = GD
}
}

private[data] sealed abstract class Tuple2KInstances0 extends Tuple2KInstances1 {
Expand Down Expand Up @@ -123,6 +128,16 @@ 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 Tuple2KContravariantMonoidal[F[_], G[_]] extends ContravariantMonoidal[λ[α => Tuple2K[F, G, α]]] {
def F: ContravariantMonoidal[F]
def G: ContravariantMonoidal[G]
def unit[A]: Tuple2K[F, G, A] = Tuple2K(F.unit, G.unit)
def product[A, B](fa: Tuple2K[F, G, A], fb: Tuple2K[F, G, B]): Tuple2K[F, G, (A, B)] =
Tuple2K(F.product(fa.first, fb.first), G.product(fa.second, fb.second))
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 Tuple2KApply[F[_], G[_]] extends Apply[λ[α => Tuple2K[F, G, α]]] with Tuple2KFunctor[F, G] {
def F: Apply[F]
def G: Apply[G]
Expand Down
22 changes: 22 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 catsDataContravariantMonoidalForWriterT[F[_], L](implicit F: ContravariantMonoidal[F]): ContravariantMonoidal[WriterT[F, L, ?]] =
new WriterTContravariantMonoidal[F, L] {
implicit val F0: ContravariantMonoidal[F] = F
}
}

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

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

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

override def contramap[A, B](fa: WriterT[F, L, A])(f: B => A): WriterT[F, L, B] =
WriterT(F0.contramap(fa.run)((d: (L, B)) => (d._1, f(d._2))))

override def product[A, B](fa: WriterT[F, L, A], fb: WriterT[F, L, B]): WriterT[F, L, (A, B)] =
WriterT(
F0.contramap(
F0.product(fa.run, fb.run))(
(t: (L, (A, B))) => t match {
case (l, (a, b)) => ((l, a), (l, b))
}))
}

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

Expand Down
27 changes: 23 additions & 4 deletions core/src/main/scala/cats/instances/eq.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,30 @@ 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 catsContravariantMonoidalForEq: ContravariantMonoidal[Eq] =
new ContravariantMonoidal[Eq] {
/**
* Defaults to the trivial equivalence relation
* contracting the type to a point
*/
def unit[A]: Eq[A] = Eq.allEqual

/** 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 contramap[A, B](fa: Eq[A])(f: B => A): Eq[B] =
Eq.instance { (l: B, r: B) =>
Copy link
Contributor

Choose a reason for hiding this comment

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

Any reason not to delegate to Eq.by, just for consistency?

fa.eqv(f(l), f(r))
}

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) }
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. 👍

(l, r) match {
case ((aL, bL), (aR, bR)) =>
fa.eqv(aL, aR) &&
fb.eqv(bL, bR)
}
}
}
}
Loading