-
-
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 Arrow Choice #2096
Add Arrow Choice #2096
Changes from 3 commits
a8de85b
f315767
7369b6f
791d9d1
93574b3
191c68c
48d9458
8b5d497
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,43 @@ | ||
package cats | ||
package arrow | ||
|
||
import simulacrum.typeclass | ||
|
||
/** | ||
* Must obey the laws defined in cats.laws.ArrowChoiceLaws. | ||
*/ | ||
@typeclass trait ArrowChoice[F[_, _]] extends Arrow[F] with Choice[F] { self => | ||
|
||
/** | ||
* ArrowChoice yields Arrows with choice, allowing distribution | ||
* over coproducts. | ||
* | ||
* Given two `F`s (`f` and `g`), create a new `F` with | ||
* domain the coproduct of the domains of `f` and `g`, | ||
* and codomain the coproduct of the codomains of `f` and `g`. | ||
* This is the sum notion to `split`'s product. | ||
* | ||
* Example: | ||
* {{{ | ||
* scala> import cats.implicits._ | ||
* scala> val toLong: Int => Long = _.toLong | ||
* scala> val toDouble: Float => Double = _.toDouble | ||
* scala> val f: Either[Int, Float] => Either[Long, Double] = toLong +++ toDouble | ||
* scala> f(Left(3)) | ||
* res0: Either[Long,Double] = Left(3) | ||
* scala> f(Right(3)) | ||
* res1: Either[Long,Double] = Right(3.0) | ||
* }}} | ||
*/ | ||
@simulacrum.op("+++", alias = true) | ||
def choose[A, B, C, D](f: F[A, C])(g: F[B, D]): F[Either[A, B], Either[C, D]] | ||
|
||
def left[A, B, C](fab: F[A, B]): F[Either[A, C], Either[B, C]] = | ||
choose(fab)(lift(identity[C])) | ||
|
||
def right[A, B, C](fab: F[A, B]): F[Either[C, A], Either[C, B]] = | ||
choose(lift(identity[C]))(fab) | ||
|
||
override def choice[A, B, C](f: F[A, C], g: F[B, C]): F[Either[A, B], C] = | ||
rmap(choose(f)(g))(_.fold(identity, identity)) | ||
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 noticed that 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. Good call, I can add. |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -130,11 +130,10 @@ private[data] sealed abstract class KleisliInstances extends KleisliInstances0 { | |
implicit def F: Monad[F] = F0 | ||
} | ||
|
||
implicit def catsDataArrowForKleisli[F[_]](implicit M: Monad[F]): Arrow[Kleisli[F, ?, ?]] = | ||
new KleisliArrow[F] { | ||
implicit def catsDataArrowChoiceForKleisli[F[_]](implicit M: Monad[F]): ArrowChoice[Kleisli[F, ?, ?]] = | ||
new KleisliArrowChoice[F] { | ||
def F: Monad[F] = M | ||
} | ||
|
||
} | ||
|
||
private[data] sealed abstract class KleisliInstances0 extends KleisliInstances1 { | ||
|
@@ -147,11 +146,10 @@ private[data] sealed abstract class KleisliInstances0 extends KleisliInstances1 | |
implicit def catsDataMonadForKleisliId[A]: Monad[Kleisli[Id, A, ?]] = | ||
catsDataMonadForKleisli[Id, A] | ||
|
||
implicit def catsDataCommutativeArrowForKleisli[F[_]](implicit M: CommutativeMonad[F]): CommutativeArrow[Kleisli[F, ?, ?]] = | ||
new KleisliCommutativeArrow[F] {def F: CommutativeMonad[F] = M } | ||
|
||
implicit val catsDataCommutativeArrowForKleisliId: CommutativeArrow[Kleisli[Id, ?, ?]] = | ||
catsDataCommutativeArrowForKleisli[Id] | ||
implicit def catsDataArrowForKleisli[F[_]](implicit M: Monad[F]): Arrow[Kleisli[F, ?, ?]] = | ||
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, since both |
||
new KleisliArrow[F] { | ||
def F: Monad[F] = M | ||
} | ||
|
||
implicit def catsDataContravariantMonoidalForKleisli[F[_], A](implicit F0: ContravariantMonoidal[F]): ContravariantMonoidal[Kleisli[F, A, ?]] = | ||
new KleisliContravariantMonoidal[F, A] { def F: ContravariantMonoidal[F] = F0 } | ||
|
@@ -161,6 +159,12 @@ private[data] sealed abstract class KleisliInstances1 extends KleisliInstances2 | |
implicit def catsDataMonadForKleisli[F[_], A](implicit M: Monad[F]): Monad[Kleisli[F, A, ?]] = | ||
new KleisliMonad[F, A] { def F: Monad[F] = M } | ||
|
||
implicit def catsDataCommutativeArrowForKleisli[F[_]](implicit M: CommutativeMonad[F]): CommutativeArrow[Kleisli[F, ?, ?]] = | ||
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. These can return 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. Sounds good, will do. |
||
new KleisliCommutativeArrow[F] {def F: CommutativeMonad[F] = M } | ||
|
||
implicit val catsDataCommutativeArrowForKleisliId: CommutativeArrow[Kleisli[Id, ?, ?]] = | ||
catsDataCommutativeArrowForKleisli[Id] | ||
|
||
implicit def catsDataParallelForKleisli[F[_], M[_], A] | ||
(implicit P: Parallel[M, F]): Parallel[Kleisli[M, A, ?], Kleisli[F, A, ?]] = new Parallel[Kleisli[M, A, ?], Kleisli[F, A, ?]]{ | ||
implicit val appF = P.applicative | ||
|
@@ -237,6 +241,18 @@ private[data] trait KleisliCommutativeArrow[F[_]] extends CommutativeArrow[Kleis | |
implicit def F: CommutativeMonad[F] | ||
} | ||
|
||
private[data] trait KleisliArrowChoice[F[_]] extends ArrowChoice[Kleisli[F, ?, ?]] with KleisliArrow[F] { | ||
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. We can fold |
||
implicit def F: Monad[F] | ||
|
||
def choose[A, B, C, D](f: Kleisli[F, A, C])(g: Kleisli[F, B, D]): Kleisli[F, Either[A, B], Either[C, D]] = | ||
Kleisli( | ||
(fe: Either[A, B]) => | ||
fe match { | ||
case Left(a) => F.map(f(a))(Left.apply _) | ||
case Right(b) => F.map(g(b))(Right.apply _) | ||
}) | ||
} | ||
|
||
private[data] trait KleisliArrow[F[_]] extends Arrow[Kleisli[F, ?, ?]] with KleisliCategory[F] with KleisliStrong[F] { | ||
implicit def F: Monad[F] | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,7 @@ package cats | |
package instances | ||
|
||
import cats.Contravariant | ||
import cats.arrow.{Category, Choice, CommutativeArrow} | ||
import cats.arrow.{Category, ArrowChoice, CommutativeArrow} | ||
|
||
import annotation.tailrec | ||
|
||
|
@@ -68,12 +68,13 @@ private[instances] sealed trait Function1Instances extends Function1Instances0 { | |
} | ||
} | ||
|
||
implicit val catsStdInstancesForFunction1: Choice[Function1] with CommutativeArrow[Function1] = | ||
new Choice[Function1] with CommutativeArrow[Function1] { | ||
def choice[A, B, C](f: A => C, g: B => C): Either[A, B] => C = { | ||
case Left(a) => f(a) | ||
case Right(b) => g(b) | ||
} | ||
implicit val catsStdInstancesForFunction1: ArrowChoice[Function1] with CommutativeArrow[Function1] = | ||
new ArrowChoice[Function1] with CommutativeArrow[Function1] { | ||
def choose[A, B, C, D](f: A => C)(g: B => D): Either[A, B] => Either[C, D] = | ||
_.fold( | ||
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. using pattern match rather than 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. Yup, can do. I tend to forget that end of things. |
||
Left.apply[C, D] _ compose f, | ||
Right.apply[C, D] _ compose g | ||
) | ||
|
||
def lift[A, B](f: A => B): A => B = f | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package cats | ||
package syntax | ||
|
||
import cats.arrow.ArrowChoice | ||
|
||
trait ArrowChoiceSyntax extends ArrowChoice.ToArrowChoiceOps |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package cats | ||
package laws | ||
|
||
import cats.arrow.ArrowChoice | ||
import cats.syntax.compose._ | ||
|
||
/** | ||
* Laws that must be obeyed by any `cats.arrow.ArrowChoice`. | ||
*/ | ||
trait ArrowChoiceLaws[F[_, _]] extends ArrowLaws[F] with ChoiceLaws[F] { | ||
implicit override def F: ArrowChoice[F] | ||
implicit def Function: ArrowChoice[Function1] | ||
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. Are these laws based on the ones defined in Control-ArrowChoice? 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. Hmm, they are meant to but interestingly the ones in the source and the ones in the Haskell doc do not quite look the same, I'll give them another pass tonight to make sure I transcribed the correct set. 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. Yeah, I totally misread them, somehow. How embarrassing! Sorry about that. Fixing. |
||
|
||
def leftLiftCommute[A, B, C](f: A => B): IsEq[F[Either[A, C], Either[B, C]]] = | ||
F.left[A, B, C](F.lift[A, B](f)) <-> F.lift[Either[A, C], Either[B, C]](Function.left[A, B, C](f)) | ||
|
||
def rightLiftCommute[A, B, C](f: A => B): IsEq[F[Either[C, A], Either[C, B]]] = | ||
F.right[A, B, C](F.lift[A, B](f)) <-> F.lift[Either[C, A], Either[C, B]](Function.right[A, B, C](f)) | ||
|
||
def chooseLiftCommute[A, B, C, D](f: A => C, g: B => D): IsEq[F[Either[A, B], Either[C, D]]] = | ||
F.lift[Either[A, B], Either[C, D]](Function.choose(f)(g)) <-> F.choose[A, B, C, D](F.lift(f))(F.lift(g)) | ||
|
||
def choiceLiftCommute[A, B, C](f: A => C, g: B => C): IsEq[F[Either[A, B], C]] = | ||
F.lift[Either[A, B], C](Function.choice[A, B, C](f, g)) <-> F.choice[A, B, C](F.lift[A, C](f), F.lift[B, C](g)) | ||
|
||
def leftComposeCommute[A, B, C, D](f: A => B, g: B => C): IsEq[F[Either[A, D], Either[C, D]]] = | ||
F.left[A, C, D](F.lift(g compose f)) <-> | ||
(F.lift[Either[B, D], Either[C, D]](Function.left(g)) <<< | ||
F.lift[Either[A, D], Either[B, D]](Function.left(f))) | ||
|
||
def rightComposeCommute[A, B, C, D](f: A => B, g: B => C): IsEq[F[Either[D, A], Either[D, C]]] = | ||
F.right(F.lift(g compose f)) <-> | ||
(F.lift[Either[D, B], Either[D, C]](Function.right(g)) <<< | ||
F.lift[Either[D, A], Either[D, B]](Function.right(f))) | ||
} | ||
|
||
object ArrowChoiceLaws { | ||
def apply[F[_, _]](implicit ev: ArrowChoice[F], f: ArrowChoice[Function1]): ArrowChoiceLaws[F] = | ||
new ArrowChoiceLaws[F] { | ||
def F: ArrowChoice[F] = ev | ||
def Function: ArrowChoice[Function1] = f | ||
} | ||
} |
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.
This is never tested by law as I noticed from the law set defined in Haskell. Thus it doesn't have any test coverage.
Shall we add a law for this something like
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 actually added symmetric versions for all the laws last night for that reason, but it takes the method well over 50 lines. Something like that sounds like a more reasonable way to get similar effect. I'll try it out today, it certainly should work.