Skip to content
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

Merged
merged 8 commits into from
Dec 13, 2017
Merged

Conversation

stephen-lazaro
Copy link
Contributor

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 +++ for choose (whose name I feel somewhat undecided about).
If coverage complains, I'll add more doc tests for the ArrowChoice methods.

@codecov-io
Copy link

codecov-io commented Dec 12, 2017

Codecov Report

Merging #2096 into master will increase coverage by 0.12%.
The diff coverage is 100%.

Impacted file tree graph

@@            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
Impacted Files Coverage Δ
core/src/main/scala/cats/arrow/Choice.scala 50% <ø> (ø) ⬆️
.../scala/cats/laws/discipline/ArrowChoiceTests.scala 100% <100%> (ø)
core/src/main/scala/cats/instances/function.scala 93.75% <100%> (ø) ⬆️
...aws/src/main/scala/cats/laws/ArrowChoiceLaws.scala 100% <100%> (ø)
core/src/main/scala/cats/arrow/ArrowChoice.scala 100% <100%> (ø)
core/src/main/scala/cats/data/Kleisli.scala 97.84% <100%> (+0.07%) ⬆️
core/src/main/scala/cats/instances/tuple.scala 100% <0%> (ø) ⬆️
... and 2 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 275f723...8b5d497. Read the comment docs.

Copy link
Contributor

@julienrf julienrf left a 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))
Copy link
Contributor

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.

Copy link
Contributor Author

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, ?, ?]] =
Copy link
Contributor

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] {
Copy link
Contributor

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, ?, ?]] =
Copy link
Contributor

@kailuowang kailuowang Dec 12, 2017

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

Copy link
Contributor Author

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]
Copy link
Contributor

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?

Copy link
Contributor Author

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.

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, I totally misread them, somehow. How embarrassing! Sorry about that. Fixing.

@kailuowang kailuowang added this to the 1.0.0 milestone Dec 12, 2017
LukaJCB
LukaJCB previously approved these changes Dec 13, 2017
Copy link
Member

@LukaJCB LukaJCB left a 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(
Copy link
Contributor

@kailuowang kailuowang Dec 13, 2017

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

Copy link
Contributor Author

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)
Copy link
Contributor

@kailuowang kailuowang Dec 13, 2017

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)

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 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.

Copy link
Contributor

@kailuowang kailuowang left a 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!

@stephen-lazaro
Copy link
Contributor Author

Thanks @kailuowang!

Copy link
Member

@LukaJCB LukaJCB left a comment

Choose a reason for hiding this comment

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

Thanks again!

@LukaJCB LukaJCB merged commit fca2014 into typelevel:master Dec 13, 2017
@stephen-lazaro stephen-lazaro mentioned this pull request Dec 15, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants