-
-
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
Conversation
Codecov Report
@@ Coverage Diff @@
## master #2096 +/- ##
==========================================
+ Coverage 94.66% 94.78% +0.12%
==========================================
Files 322 325 +3
Lines 5451 5487 +36
Branches 215 207 -8
==========================================
+ Hits 5160 5201 +41
+ Misses 291 286 -5
Continue to review full report at Codecov.
|
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.
Note that, like Arrow[A, ?]
has an SemiGroupal
instance (which has a product[A, B](fa: F[A], fb: F[B]): F[(A, B)]
), ArrowChoice[A, ?]
has a CoSemiGroupal
instance (which would have a sum[A, B](fa: F[A], fb: F[B]): F[Either[A, B]]
operation). I don’t know if there is an established name for CoSemiGroupal
, nor if this sum
method is useful, but I like the symmetry :)
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 comment
The reason will be displayed to describe this comment to others. Learn more.
I noticed that choice
doesn’t have |||
as a symbolic alias. This is not really related to this PR but that would be a good thing to have.
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.
Good call, I can add.
|
||
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 comment
The reason will be displayed to describe this comment to others. Learn more.
If I am not mistaken, since both Arrow
and ArrowChoice
instances have the same requirement (Monad[F]
), one ArrowChoice
instance should be fine. You can basically leave catsDataCommutativeArrowForKleisliId
and catsDataCommutativeArrowForKleisli
as is.
@@ -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 comment
The reason will be displayed to describe this comment to others. Learn more.
We can fold KleisliArrow
into KleisliArrowChoice
right? KleisliCommutativeArrow
can extend KleisliArrowChoice
.
@@ -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 comment
The reason will be displayed to describe this comment to others. Learn more.
These can return CommutativeArrow with ArrowChoice
, if we let KleisliCommutativeArrow
extend KleisliArrowChoice
as mentioned below
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.
Sounds good, will do.
*/ | ||
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 comment
The 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 comment
The 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 comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I totally misread them, somehow. How embarrassing! Sorry about that. Fixing.
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 looks great!
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 comment
The reason will be displayed to describe this comment to others. Learn more.
using pattern match rather than fold
has performance gain. see #1951
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.
Yup, can do. I tend to forget that end of things.
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) |
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
def leftRightConsistent[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.left[A, B, C](F.lift[A, B](f)).dimap(_.swap, _.swap)
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.
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.
Thanks so much @stephen-lazaro! Awesome contribution!
Thanks @kailuowang! |
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.
Thanks again!
Coming in right under the wire, this resolves issue #2067.
The laws are admittedly hideous, but seem morally correct. I believe I caught all the binary incompatibilities.
I also updated the symbols table with the new
+++
forchoose
(whose name I feel somewhat undecided about).If coverage complains, I'll add more doc tests for the
ArrowChoice
methods.