-
-
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 1 commit
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,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] | ||
|
||
|
||
// 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) | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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) => | ||
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. 👍 |
||
(f(l), f(r)) match { | ||
case (derivedL, derivedR) => | ||
fb.eqv(derivedL._1, derivedR._1) && | ||
fc.eqv(derivedL._2, derivedR._2) | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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) | ||
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. 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) | ||
} | ||
} | ||
} | ||
} |
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.
Why not provide a default implementation in terms of
contramap
andproduct
? (like we do for covariant semigroupal: https://github.com/typelevel/cats/blob/master/core/src/main/scala/cats/Apply.scala#L44-L50 )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.
Sure, that makes sense, consistency is pretty valuable. I mostly went this approach based on the primary document.