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
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) =
Copy link
Member

Choose a reason for hiding this comment

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

Can you add type annotations here and for product? :)

F.map(fa)(G.contramap(_)(f))

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

Choose a reason for hiding this comment

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

We need a space after the comma here :)

}

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

import simulacrum.typeclass

/**
* ContravariantMonoidal functors
*
* 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)

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

// 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
}
}

3 changes: 3 additions & 0 deletions core/src/main/scala/cats/ContravariantSemigroupal.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,7 @@ import simulacrum.typeclass
def F = self
def G = Functor[G]
}

def contramap2[A, B, C](fb: F[B], fc: F[C])(f: A => (B, C)): F[A] =
Copy link
Member

Choose a reason for hiding this comment

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

I'm not exactly sure if we need this definition here, as we already have contramap2- contramap22 in the auto-generated SemigroupalArityFunctions. WDYT?

Copy link
Member

Choose a reason for hiding this comment

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

We could mix in the SemigroupalArityFunctions into the companion object of ContravariantSemigroupal maybe?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, yes! I agree. I didn't realize they were in SemigroupalArityFunctions. I'll do that.

contramap(product(fb, fc))(f)
}
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)

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
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)
}
}
}
}
27 changes: 22 additions & 5 deletions core/src/main/scala/cats/instances/equiv.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,34 @@ package cats
package instances

trait EquivInstances {
implicit val catsContravariantSemigroupalEquiv: ContravariantSemigroupal[Equiv] =
new ContravariantSemigroupal[Equiv] {
implicit val catsContravariantMonoidalForEquiv: ContravariantMonoidal[Equiv] =
new ContravariantMonoidal[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
}

/** 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 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))
def equiv(l: B, r: B): Boolean =
fa.equiv(f(l), f(r))
}

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

def equiv(l: (A, B), r: (A, B)): Boolean =
(l, r) match {
case ((aL, bL), (aR, bR)) =>
fa.equiv(aL, aR) &&
fb.equiv(bR, bL)
}
}
}
}
11 changes: 11 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,17 @@ private[instances] sealed trait Function1Instances {
fa.compose(f)
}

implicit def catsStdContravariantMonoidalForFunction1[R: Monoid]: ContravariantMonoidal[? => R] =
new ContravariantMonoidal[? => R] {
def unit[A]: A => R = Function.const(Monoid[R].empty)
def contramap[A, B](fa: A => R)(f: B => A): B => R =
fa compose f
def product[A, B](fa: A => R, fb: B => R): ((A, B)) => R =
(ab: (A, B)) => ab match {
case (a, b) => Monoid[R].combine(fa(a), fb(b))
}
}

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