-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Changes from 7 commits
0501b2c
3db8a82
afd8aef
d03cfd5
cdf5732
caf2a40
81e7f06
2e8c20a
a27927d
9b09aa4
1c14af2
110ac5e
f7c4104
6e7a1b5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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] = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can actually be moved to |
||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ah, I propose we rename and move it to There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 { | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,3 +13,5 @@ import simulacrum.typeclass | |
def G = Functor[G] | ||
} | ||
} | ||
object ContravariantSemigroupal extends SemigroupalArityFunctions { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nitpick, but can we get rid of these curly braces? :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hah, yeah, for sure. |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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, ?]] = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 { | ||
|
@@ -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] | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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) => | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) => | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 :-)) There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
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 becauseApplicative
already impliesMonoidal
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.
There was a problem hiding this comment.
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.