Skip to content

Add Decidable #2607

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

Open
wants to merge 91 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
91 commits
Select commit Hold shift + click to select a range
289c2fb
Add Decideable
stephen-lazaro Nov 5, 2018
ab2105f
Add first batch of instances
stephen-lazaro Nov 5, 2018
b290417
Another batch of instances
stephen-lazaro Nov 7, 2018
8ebe564
Tuple2K instance
stephen-lazaro Nov 7, 2018
4ae9ba0
IdT instance
stephen-lazaro Nov 7, 2018
6ceae06
Draft law
stephen-lazaro Nov 8, 2018
764b8ca
Some instance fixes...
stephen-lazaro Nov 10, 2018
04b1433
Scale back those insane laws
stephen-lazaro Nov 10, 2018
57abb07
Initial tests...
stephen-lazaro Nov 10, 2018
ad4c062
First pass at docs
stephen-lazaro Nov 10, 2018
244e7a5
Retuned laws with mostly reasonable right distributivity
stephen-lazaro Nov 10, 2018
a8f8079
Comment out dead code warning
stephen-lazaro Nov 10, 2018
164bc81
Hmmmm
stephen-lazaro Nov 12, 2018
a14d7c4
Fix implicit conflict
stephen-lazaro Jul 2, 2019
0a6fbf5
Fix style and so forth
stephen-lazaro Jul 2, 2019
b310dba
Merge remote-tracking branch 'typelevel/master' into slazaro/decideable
stephen-lazaro Jul 2, 2019
5b5f04b
Fix everything but MIMA
stephen-lazaro Jul 2, 2019
79f96a8
Merge branch 'master' into slazaro/decideable
stephen-lazaro Jul 21, 2020
45fe0c4
Format and fix OptionT instance
stephen-lazaro Jul 21, 2020
f3ce9ec
More lintings
stephen-lazaro Jul 21, 2020
2d38c82
Fix build settings
stephen-lazaro Jul 21, 2020
26d812d
Undo weird thing
stephen-lazaro Jul 21, 2020
ce3f380
Double down
stephen-lazaro Jul 21, 2020
289588a
Try and get better cross-version compat
stephen-lazaro Jul 21, 2020
f9d8f18
And more so
stephen-lazaro Jul 21, 2020
ec6920b
Fix some version specific instances
stephen-lazaro Jul 21, 2020
4af7db2
Pretty weird stuff I guess
stephen-lazaro Jul 21, 2020
8a99300
Formatting
stephen-lazaro Jul 21, 2020
a27b391
Another linting
stephen-lazaro Jul 21, 2020
39e6395
Fix tut probably and remove Const instance
stephen-lazaro Jul 21, 2020
d94ad94
Some clean up, breaks tests
stephen-lazaro Jul 22, 2020
7e357aa
Missed a bit
stephen-lazaro Jul 22, 2020
12ef1f9
Significantly improved tests
stephen-lazaro Jul 22, 2020
af15b3c
Formatter
stephen-lazaro Jul 22, 2020
ea48c08
Improve some instances, drop the product sum law
stephen-lazaro Jul 28, 2020
a0325e8
Fix laws references
stephen-lazaro Jul 28, 2020
c8ff7b8
Add back law and weaken some instances
stephen-lazaro Jul 28, 2020
55bdd91
Fix a test
stephen-lazaro Jul 28, 2020
692c3e0
Clean flags
stephen-lazaro Jul 28, 2020
85d29ef
Clean some imports
stephen-lazaro Jul 28, 2020
beb03d6
Improve binary compat
stephen-lazaro Jul 28, 2020
dccb090
Try to fix some mima issues
stephen-lazaro Jul 28, 2020
d88f178
Baffling that that works
stephen-lazaro Jul 28, 2020
d67ee5c
Formatting
stephen-lazaro Jul 29, 2020
2ebeb45
Merge branch 'main' into slazaro/decideable
stephen-lazaro Jun 26, 2022
b64f3f8
Fix merge
stephen-lazaro Jun 26, 2022
45966c3
Formatting
stephen-lazaro Jun 26, 2022
91962c4
Heaaders for rights
stephen-lazaro Jun 27, 2022
72289b1
Clean docs and suchlike
stephen-lazaro Jun 27, 2022
87fe236
No implicitNotFound
stephen-lazaro Jun 27, 2022
3d96d06
Accidental newline
stephen-lazaro Jun 27, 2022
8ab26f1
s/INothing/Nothing/g
stephen-lazaro Jun 27, 2022
9edb1e4
Make some things vals, re-organize methods
stephen-lazaro Jun 28, 2022
ffe0459
Avoid binary incompat
stephen-lazaro Jun 28, 2022
8bcaa2e
Make alias final
stephen-lazaro Jun 28, 2022
da8224a
Another val
stephen-lazaro Jun 28, 2022
b051a4d
More vals, some docs cleaning
stephen-lazaro Jun 29, 2022
c787ae8
Remove Haskell style name aliases
stephen-lazaro Jun 29, 2022
13570c7
Another val
stephen-lazaro Jun 29, 2022
7cc84b7
Equiv `val`
stephen-lazaro Jun 29, 2022
fef5ff0
Correct for managed code no longer managed
stephen-lazaro Jun 29, 2022
011c8eb
Use simpler expression in Composed
stephen-lazaro Jun 29, 2022
7066dbd
Adjust initialization, clean a couple tests
stephen-lazaro Jun 30, 2022
cb51073
Fix inter-doc link
stephen-lazaro Jul 1, 2022
92dfde7
Fix JS linking in tests
stephen-lazaro Jul 1, 2022
2844752
Add a couple MIMA exceptions
stephen-lazaro Jul 1, 2022
0e3ebe8
Actually fix bincompat, not exclude
stephen-lazaro Jul 2, 2022
3a0b2c4
Fix the formatting
stephen-lazaro Jul 2, 2022
7730deb
Update CONTRIBUTING with `prePR`
stephen-lazaro Jul 2, 2022
68c27f2
Commit suggested doc comment change
stephen-lazaro Jul 6, 2022
4814ed5
Commit link to original source change
stephen-lazaro Jul 6, 2022
3daa7e1
Clean and modernize syntax
stephen-lazaro Jul 6, 2022
587f274
Fix syntax modernization and add deprecations on old instances
stephen-lazaro Jul 6, 2022
73033a1
s/ReaderT/Kleisli/
stephen-lazaro Jul 6, 2022
6e75fba
Undo some instance re-arranging that is no longer relevant
stephen-lazaro Jul 6, 2022
ebf2acf
Eliminate unnecessary names match
stephen-lazaro Jul 6, 2022
bf5e352
Simpler bincompat strategy for Eq and Equiv
stephen-lazaro Jul 6, 2022
8d61434
Fix docsite a bit
stephen-lazaro Jul 6, 2022
bcf4801
Remove some odd instance scoping in tests
stephen-lazaro Jul 6, 2022
5274449
Remove companion object from docsite
stephen-lazaro Jul 6, 2022
e06a780
Define test Predicate instance via std instance
stephen-lazaro Jul 6, 2022
0938926
Law for `decide` consistency
stephen-lazaro Jul 12, 2022
9620658
Add a law for zero
stephen-lazaro Jul 12, 2022
019ebf7
A couple identity laws
stephen-lazaro Jul 12, 2022
a9e56b9
Simplify zero identity laws
stephen-lazaro Jul 12, 2022
a60cadd
Possibly rigged versions of the `Nothing` tests
stephen-lazaro Jul 12, 2022
00a6cd9
Re-structure Nothing based constraints in tests
stephen-lazaro Jul 21, 2022
3ef22ed
Comment on weird instance, shrug shoulders
stephen-lazaro Jul 21, 2022
0c9c090
Delete right absorption
stephen-lazaro Aug 18, 2022
70f42df
Corrected Eq
stephen-lazaro Aug 18, 2022
d5be88a
Merge branch 'main' into slazaro/decideable
stephen-lazaro Oct 30, 2022
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
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -732,7 +732,7 @@ def commonScalacOptions(scalaVersion: String) =
"-language:implicitConversions",
"-language:experimental.macros",
"-unchecked",
"-Ywarn-dead-code",
//"-Ywarn-dead-code",
"-Ywarn-numeric-widen",
"-Ywarn-value-discard",
"-Xfuture"
Expand Down
6 changes: 6 additions & 0 deletions core/src/main/scala/cats/Applicative.scala
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,12 @@ import simulacrum.typeclass
val G = ContravariantMonoidal[G]
}

def composeDecideable[G[_]: Decideable]: Decideable[λ[α => F[G[α]]]] =
new ComposedApplicativeDecideable[F, G] {
val F = self
val G = Decideable[G]
}

/**
* Returns the given argument (mapped to Unit) if `cond` is `false`,
* otherwise, unit lifted into F.
Expand Down
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 @@ -131,6 +131,16 @@ private[cats] trait ComposedContravariantCovariant[F[_], G[_]] extends Contravar
F.contramap(fga)(gb => G.map(gb)(f))
}

private[cats] trait ComposedApplicativeDecideable[F[_], G[_]]
extends Decideable[λ[α => F[G[α]]]]
with ComposedApplicativeContravariantMonoidal[F, G] { outer =>
def F: Applicative[F]
def G: Decideable[G]

def sum[A, B](fa: F[G[A]], fb: F[G[B]]): F[G[Either[A, B]]] =
F.map(F.product(fa, fb))(Function.tupled(G.sum))
}

private[cats] trait ComposedApplicativeContravariantMonoidal[F[_], G[_]]
extends ContravariantMonoidal[λ[α => F[G[α]]]] { outer =>
def F: Applicative[F]
Expand Down
27 changes: 27 additions & 0 deletions core/src/main/scala/cats/Decideable.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package cats

import simulacrum.typeclass

/**
* [[Decideable]] functors are functors that supply
* a `decide` operation allowing choices to be made.
*
* This is comparable to [[Alternative]] in the
* covariant case.
*
* Must obey laws in cats.laws.DecideableLaws.
*
* Based on ekmett's contravariant library:
* https://hackage.haskell.org/package/contravariant-1.4/docs/Data-Functor-Contravariant-Divisible.html#g:2
*/
@typeclass trait Decideable[F[_]] extends ContravariantMonoidal[F] {
def sum[A, B](fa: F[A], fb: F[B]): F[Either[A, B]]

def decide[A, B, C](fa: F[A], fb: F[B])(f: C => Either[A, B]): F[C] =
contramap(sum(fa, fb))(f)

def chosen[B, C](fb: F[B], fc: F[C]): F[Either[B, C]] =
decide(fb, fc)(identity[Either[B, C]])

def zero: F[Nothing] = trivial[Nothing]
Copy link
Member

Choose a reason for hiding this comment

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

Are these two always the same? I feel like it's a bit weird that both the additive and multiplicative identities are the same, but it might very well be that my intuition for these sorts of things are off 🤔

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 am a bit suspicious of it as well... For example, the case of maps into Semirings that doesn't quite feel like the right choice... It might bear more thinking on.

Copy link
Member

@LukaJCB LukaJCB Nov 13, 2018

Choose a reason for hiding this comment

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

Yeah, I think we might need to change this. I think for any [A, B: Semiring]: A => B you'll want unit to be A => 1 and zero to be A => 0. With this default, zero would be the same as unit, which we don't want.
I think we can already see this in this PR, where you explicitly override zero in the Predicate instance (which can be seen as a specialization of [A, B: Semiring]: A => B) in order for it to be const(false) instead of const(true) :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, that was sort of my intuition as well. I'm a bit puzzled at the original's choice to use Void but, there's probably some reason. Regardless, will change.

Copy link
Contributor

Choose a reason for hiding this comment

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

See my comment in #2620 (comment)

}
17 changes: 15 additions & 2 deletions core/src/main/scala/cats/data/Const.scala
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ final case class Const[A, B](getConst: A) {
s"Const(${A.show(getConst)})"
}

object Const extends ConstInstances {
object Const extends ConstInstances with ConstInstancesBinCompat0 {
def empty[A, B](implicit A: Monoid[A]): Const[A, B] =
Const(A.empty)

Expand Down Expand Up @@ -121,7 +121,6 @@ sealed abstract private[data] class ConstInstances extends ConstInstances0 {
}

sealed abstract private[data] class ConstInstances0 extends ConstInstances1 {

implicit def catsDataContravariantMonoidalForConst[D: Monoid]: ContravariantMonoidal[Const[D, ?]] =
new ContravariantMonoidal[Const[D, ?]] {
override def unit = Const.empty[D, Unit]
Expand Down Expand Up @@ -179,6 +178,20 @@ sealed abstract private[data] class ConstInstances4 {
new ConstContravariant[C] {}
}

trait ConstInstancesBinCompat0 {
implicit def catsDataDecideableForConst[D: Monoid]: Decideable[Const[D, ?]] =
new Decideable[Const[D, ?]] {
override def unit = Const.empty[D, Unit]
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)])
def sum[A, B](fa: Const[D, A], fb: Const[D, B]): Const[D, Either[A, B]] =
fa.retag[Either[A, B]].combine(fb.retag[Either[A, B]])
}

}

sealed private[data] trait ConstFunctor[C] extends Functor[Const[C, ?]] {
def map[A, B](fa: Const[C, A])(f: A => B): Const[C, B] =
fa.retag[B]
Expand Down
13 changes: 13 additions & 0 deletions core/src/main/scala/cats/data/IdT.scala
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,13 @@ sealed private[data] trait IdTApplicative[F[_]] extends Applicative[IdT[F, ?]] w
def pure[A](a: A): IdT[F, A] = IdT.pure(a)
}

sealed private[data] trait IdTDecideable[F[_]] extends Decideable[IdT[F, ?]] with IdTContravariantMonoidal[F] {
implicit val F0: Decideable[F]

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

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

Expand Down Expand Up @@ -148,6 +155,7 @@ sealed abstract private[data] class IdTInstances8 {
sealed abstract private[data] class IdTInstances7 extends IdTInstances8 {
implicit def catsDataCommutativeMonadForIdT[F[_]](implicit F: CommutativeMonad[F]): CommutativeMonad[IdT[F, ?]] =
new IdTMonad[F] with CommutativeMonad[IdT[F, ?]] { implicit val F0: CommutativeMonad[F] = F }

}

sealed abstract private[data] class IdTInstances6 extends IdTInstances7 {
Expand All @@ -160,6 +168,11 @@ sealed abstract private[data] class IdTInstances6 extends IdTInstances7 {
sealed abstract private[data] class IdTInstances5 extends IdTInstances6 {
implicit def catsDataFunctorForIdT[F[_]](implicit F: Functor[F]): Functor[IdT[F, ?]] =
new IdTFunctor[F] { implicit val F0: Functor[F] = F }

implicit def catsDataDecideableForIdT[F[_]](
implicit F: Decideable[F]
): Decideable[IdT[F, ?]] =
new IdTDecideable[F] { implicit val F0: Decideable[F] = F }
}

sealed abstract private[data] class IdTInstances4 extends IdTInstances5 {
Expand Down
13 changes: 13 additions & 0 deletions core/src/main/scala/cats/data/Kleisli.scala
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,10 @@ sealed abstract private[data] class KleisliInstances0 extends KleisliInstances0_
implicit def F: Monad[F] = F0
}

implicit def catsDataDecideableForKleisli[F[_], A](
implicit F0: Decideable[F]
): Decideable[Kleisli[F, A, ?]] =
new KleisliDecideable[F, A] { def F: Decideable[F] = F0 }
}

sealed abstract private[data] class KleisliInstances0_5 extends KleisliInstances1 {
Expand Down Expand Up @@ -419,6 +423,15 @@ private[data] trait KleisliAlternative[F[_], A]
implicit def F: Alternative[F]
}

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

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

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

Expand Down
23 changes: 19 additions & 4 deletions core/src/main/scala/cats/data/Nested.scala
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ sealed abstract private[data] class NestedInstances extends NestedInstances0 {
val FG: NonEmptyTraverse[λ[α => F[G[α]]]] = NonEmptyTraverse[F].compose[G]
}

implicit def catsDataContravariantMonoidalForApplicativeForNested[F[_]: Applicative, G[_]: ContravariantMonoidal]
: ContravariantMonoidal[Nested[F, G, ?]] =
new NestedContravariantMonoidal[F, G] with NestedContravariant[F, G] {
val FG: ContravariantMonoidal[λ[α => F[G[α]]]] = Applicative[F].composeContravariantMonoidal[G]
implicit def catsDataDecideableForApplicativeForNested[F[_]: Applicative, G[_]: Decideable]
: Decideable[Nested[F, G, ?]] =
new NestedDecideable[F, G] with NestedContravariant[F, G] {
val FG: Decideable[λ[α => F[G[α]]]] = Applicative[F].composeDecideable[G]
}

implicit def catsDataDeferForNested[F[_], G[_]](implicit F: Defer[F]): Defer[Nested[F, G, ?]] =
Expand Down Expand Up @@ -75,6 +75,12 @@ sealed abstract private[data] class NestedInstances0 extends NestedInstances1 {
implicit val F: Functor[F] = F0
implicit val G: FunctorFilter[G] = G0
}

implicit def catsDataContravariantMonoidalForApplicativeForNested[F[_]: Applicative, G[_]: ContravariantMonoidal]
: ContravariantMonoidal[Nested[F, G, ?]] =
new NestedContravariantMonoidal[F, G] with NestedContravariant[F, G] {
val FG: ContravariantMonoidal[λ[α => F[G[α]]]] = Applicative[F].composeContravariantMonoidal[G]
}
}

sealed abstract private[data] class NestedInstances1 extends NestedInstances2 {
Expand Down Expand Up @@ -329,6 +335,15 @@ private[data] trait NestedContravariant[F[_], G[_]] extends Contravariant[Nested
Nested(FG.contramap(fga.value)(f))
}

private[data] trait NestedDecideable[F[_], G[_]]
extends Decideable[Nested[F, G, ?]]
with NestedContravariantMonoidal[F, G] {
def FG: Decideable[λ[α => F[G[α]]]]

def sum[A, B](fa: Nested[F, G, A], fb: Nested[F, G, B]): Nested[F, G, Either[A, B]] =
Nested(FG.sum(fa.value, fb.value))
}

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

Expand Down
46 changes: 45 additions & 1 deletion core/src/main/scala/cats/data/Op.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,29 @@ sealed abstract private[data] class OpInstances extends OpInstances0 {
implicit def catsDataCategoryForOp[Arr[_, _]](implicit ArrC: Category[Arr]): Category[Op[Arr, ?, ?]] =
new OpCategory[Arr] { def Arr: Category[Arr] = ArrC }

implicit def catsDataDecideableForOp[Arr[_, _], R](implicit ArrC: ArrowChoice[Arr],
monn: Monoid[R]): Decideable[Op[Arr, R, ?]] =
new OpDecideable[Arr, R] {
def Arr: ArrowChoice[Arr] = ArrC
def M: Monoid[R] = monn
}

implicit def catsKernelEqForOp[Arr[_, _], A, B](implicit ArrEq: Eq[Arr[B, A]]): Eq[Op[Arr, A, B]] =
new OpEq[Arr, A, B] { def Arr: Eq[Arr[B, A]] = ArrEq }
}

sealed abstract private[data] class OpInstances0 {
sealed abstract private[data] class OpInstances0 extends OpInstances1 {
implicit def catsDataComposeForOp[Arr[_, _]](implicit ArrC: Compose[Arr]): Compose[Op[Arr, ?, ?]] =
new OpCompose[Arr] { def Arr: Compose[Arr] = ArrC }

implicit def catsDataContravariantMonoidalForOp[Arr[_, _], R](implicit ArrC: Arrow[Arr],
M0: Monoid[R]): ContravariantMonoidal[Op[Arr, R, ?]] =
new OpContravariantMonoidal[Arr, R] { def Arr = ArrC; def M = M0 }
}

sealed abstract private[data] class OpInstances1 {
implicit def catsDataContravariantForOp[Arr[_, _], R](implicit ArrC: Arrow[Arr]): Contravariant[Op[Arr, R, ?]] =
new OpContravariant[Arr, R] { def Arr = ArrC }
}

private[data] trait OpCategory[Arr[_, _]] extends Category[Op[Arr, ?, ?]] with OpCompose[Arr] {
Expand All @@ -42,6 +58,34 @@ private[data] trait OpCompose[Arr[_, _]] extends Compose[Op[Arr, ?, ?]] {
f.compose(g)
}

private[data] trait OpDecideable[Arr[_, _], R] extends Decideable[Op[Arr, R, ?]] with OpContravariantMonoidal[Arr, R] {
implicit def Arr: ArrowChoice[Arr]
implicit def M: Monoid[R]

def sum[A, B](fa: Op[Arr, R, A], fb: Op[Arr, R, B]): Op[Arr, R, Either[A, B]] =
Op(Arr.choice(fa.run, fb.run))
}

private[data] trait OpContravariantMonoidal[Arr[_, _], R]
extends OpContravariant[Arr, R]
with ContravariantMonoidal[Op[Arr, R, ?]] {
implicit def Arr: Arrow[Arr]
implicit def M: Monoid[R]

def unit: Op[Arr, R, Unit] =
Op(Arr.lift(Function.const(M.empty)))

def product[A, B](fa: Op[Arr, R, A], fb: Op[Arr, R, B]): Op[Arr, R, (A, B)] =
Op(Arr.compose(Arr.lift((M.combine _).tupled), Arr.split(fa.run, fb.run)))
}

private[data] trait OpContravariant[Arr[_, _], R] extends Contravariant[Op[Arr, R, ?]] {
implicit def Arr: Arrow[Arr]

def contramap[A, B](fa: Op[Arr, R, A])(f: B => A): Op[Arr, R, B] =
Op(Arr.compose(fa.run, Arr.lift(f)))
}

private[data] trait OpEq[Arr[_, _], A, B] extends Eq[Op[Arr, A, B]] {
implicit def Arr: Eq[Arr[B, A]]

Expand Down
28 changes: 24 additions & 4 deletions core/src/main/scala/cats/data/OptionT.scala
Original file line number Diff line number Diff line change
Expand Up @@ -273,10 +273,8 @@ sealed abstract private[data] class OptionTInstances0 extends OptionTInstances1
implicit def catsDataMonadErrorMonadForOptionT[F[_]](implicit F0: Monad[F]): MonadError[OptionT[F, ?], Unit] =
new OptionTMonadErrorMonad[F] { implicit val F = F0 }

implicit def catsDataContravariantMonoidalForOptionT[F[_]](
implicit F0: ContravariantMonoidal[F]
): ContravariantMonoidal[OptionT[F, ?]] =
new OptionTContravariantMonoidal[F] { implicit val F = F0 }
implicit def catsDataDecideableForOptionT[F[_]](implicit F0: Decideable[F]): Decideable[OptionT[F, ?]] =
new OptionTDecideable[F] { implicit val F = F0 }

implicit def catsDataMonoidKForOptionT[F[_]](implicit F0: Monad[F]): MonoidK[OptionT[F, ?]] =
new OptionTMonoidK[F] { implicit val F = F0 }
Expand Down Expand Up @@ -305,6 +303,12 @@ sealed abstract private[data] class OptionTInstances1 extends OptionTInstances2

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 }

}

sealed abstract private[data] class OptionTInstances2 extends OptionTInstances3 {
Expand Down Expand Up @@ -382,6 +386,22 @@ private trait OptionTMonadError[F[_], E] extends MonadError[OptionT[F, ?], E] wi
OptionT(F.handleErrorWith(fa.value)(f(_).value))
}

private trait OptionTDecideable[F[_]] extends Decideable[OptionT[F, ?]] with OptionTContravariantMonoidal[F] {
def F: Decideable[F]

def sum[A, B](fa: OptionT[F, A], fb: OptionT[F, B]): OptionT[F, Either[A, B]] =
OptionT(
F.decide(fa.value, fb.value)(
(e: Option[Either[A, B]]) =>
e match {
case Some(Right(b)) => Right(Option(b))
case Some(Left(a)) => Left(Option(a))
case None => Left(None)
}
)
)
}

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

Expand Down
Loading