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
Show file tree
Hide file tree
Changes from all commits
Commits
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
18 changes: 18 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,24 @@ def mimaSettings(moduleName: String) = Seq(
exclude[ReversedMissingMethodProblem]("cats.data.CommonIRWSTConstructors.liftK"),
exclude[ReversedMissingMethodProblem]("cats.data.KleisliFunctions.liftF"),
exclude[ReversedMissingMethodProblem]("cats.data.KleisliFunctions.liftK"),
exclude[IncompatibleResultTypeProblem]("cats.implicits.catsStdInstancesForFunction1"),
exclude[IncompatibleResultTypeProblem]("cats.instances.package#all.catsStdInstancesForFunction1"),
exclude[IncompatibleResultTypeProblem]("cats.instances.Function1Instances.catsStdInstancesForFunction1"),
exclude[ReversedMissingMethodProblem]("cats.instances.Function1Instances.catsStdInstancesForFunction1"),
exclude[ReversedMissingMethodProblem]("cats.instances.Function1Instances.cats$instances$Function1Instances$_setter_$catsStdInstancesForFunction1_="),
exclude[IncompatibleResultTypeProblem]("cats.instances.package#function.catsStdInstancesForFunction1"),
exclude[InheritedNewAbstractMethodProblem]("cats.arrow.ArrowChoice#ToArrowChoiceOps.toArrowChoiceOps"),
exclude[DirectMissingMethodProblem]("cats.data.KleisliInstances.catsDataArrowForKleisli"),
exclude[MissingClassProblem]("cats.data.KleisliArrow"),
exclude[MissingTypesProblem]("cats.data.KleisliCommutativeArrow"),
exclude[InheritedNewAbstractMethodProblem]("cats.arrow.ArrowChoice.choose"),
exclude[InheritedNewAbstractMethodProblem]("cats.arrow.ArrowChoice.choice"),
exclude[InheritedNewAbstractMethodProblem]("cats.arrow.Choice.codiagonal"),
exclude[InheritedNewAbstractMethodProblem]("cats.arrow.Choice.choice"),
exclude[InheritedNewAbstractMethodProblem]("cats.data.KleisliArrowChoice.choose"),
exclude[InheritedNewAbstractMethodProblem]("cats.arrow.ArrowChoice.left"),
exclude[InheritedNewAbstractMethodProblem]("cats.arrow.ArrowChoice.right"),
exclude[ReversedMissingMethodProblem]("cats.arrow.Choice#Ops.|||"),
exclude[ReversedMissingMethodProblem]("cats.data.CommonStateTConstructors.liftF"),
exclude[ReversedMissingMethodProblem]("cats.data.CommonStateTConstructors.liftK"),
exclude[ReversedMissingMethodProblem]("cats.NonEmptyParallel.parForEffect"),
Expand Down
43 changes: 43 additions & 0 deletions core/src/main/scala/cats/arrow/ArrowChoice.scala
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)
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.


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.

}
1 change: 1 addition & 0 deletions core/src/main/scala/cats/arrow/Choice.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import simulacrum.typeclass
* res0: String = false is a boolean
* }}}
*/
@simulacrum.op("|||", alias = true)
def choice[A, B, C](f: F[A, C], g: F[B, C]): F[Either[A, B], C]

/**
Expand Down
29 changes: 18 additions & 11 deletions core/src/main/scala/cats/data/Kleisli.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -147,12 +146,6 @@ 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 catsDataContravariantMonoidalForKleisli[F[_], A](implicit F0: ContravariantMonoidal[F]): ContravariantMonoidal[Kleisli[F, A, ?]] =
new KleisliContravariantMonoidal[F, A] { def F: ContravariantMonoidal[F] = F0 }
}
Expand All @@ -161,6 +154,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, ?, ?]] with ArrowChoice[Kleisli[F, ?, ?]] =
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
Expand Down Expand Up @@ -238,18 +237,26 @@ private[data] sealed abstract class KleisliInstances8 {
new KleisliFunctor[F, A] { def F: Functor[F] = F0 }
}

private[data] trait KleisliCommutativeArrow[F[_]] extends CommutativeArrow[Kleisli[F, ?, ?]] with KleisliArrow[F] {
private[data] trait KleisliCommutativeArrow[F[_]] extends CommutativeArrow[Kleisli[F, ?, ?]] with KleisliArrowChoice[F] {
implicit def F: CommutativeMonad[F]
}

private[data] trait KleisliArrow[F[_]] extends Arrow[Kleisli[F, ?, ?]] with KleisliCategory[F] with KleisliStrong[F] {
private[data] trait KleisliArrowChoice[F[_]] extends ArrowChoice[Kleisli[F, ?, ?]] with KleisliCategory[F] with KleisliStrong[F] {
implicit def F: Monad[F]

def lift[A, B](f: A => B): Kleisli[F, A, B] =
Kleisli(a => F.pure(f(a)))

override def split[A, B, C, D](f: Kleisli[F, A, B], g: Kleisli[F, C, D]): Kleisli[F, (A, C), (B, D)] =
Kleisli{ case (a, c) => F.flatMap(f.run(a))(b => F.map(g.run(c))(d => (b, d))) }

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 KleisliStrong[F[_]] extends Strong[Kleisli[F, ?, ?]] {
Expand Down
16 changes: 8 additions & 8 deletions core/src/main/scala/cats/instances/function.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -76,13 +76,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] =
_ match {
case Left(a) => Left(f(a))
case Right(b) => Right(g(b))
}

def lift[A, B](f: A => B): A => B = f

Expand Down
1 change: 1 addition & 0 deletions core/src/main/scala/cats/syntax/all.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ trait AllSyntax
with ApplicativeErrorSyntax
with ApplySyntax
with ArrowSyntax
with ArrowChoiceSyntax
with BifunctorSyntax
with BifoldableSyntax
with BitraverseSyntax
Expand Down
6 changes: 6 additions & 0 deletions core/src/main/scala/cats/syntax/arrowChoice.scala
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
1 change: 1 addition & 0 deletions core/src/main/scala/cats/syntax/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package object syntax {
object applicativeError extends ApplicativeErrorSyntax
object apply extends ApplySyntax
object arrow extends ArrowSyntax
object arrowChoice extends ArrowChoiceSyntax
object bifunctor extends BifunctorSyntax
object bifoldable extends BifoldableSyntax
object bitraverse extends BitraverseSyntax
Expand Down
56 changes: 29 additions & 27 deletions docs/src/main/tut/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,33 +211,35 @@ The `~>`, `⊥`, `⊤`, `:<:` and `:≺:` symbols can be imported with `import c

All other symbols can be imported with `import cats.implicits._`

| Symbol | Name | Nickname | Type Class | Signature |
| -------------------------------- | -------------------------| ---------------- | ----------------------- | --------------------------------------------------------------------|
| `fa *> fb` | followed by | | `Apply[F[_]]` | `followedBy(fa: F[A])(fb: F[B]): F[B]` |
| `fa <* fb` | for effect | | `Apply[F[_]]` | `forEffect(fa: F[A])(fb: F[B]): F[A]` |
| `x === y` | equals | | `Eq[A]` | `eqv(x: A, y: A): Boolean` |
| `x =!= y` | not equals | | `Eq[A]` | `neqv(x: A, y: A): Boolean` |
| `fa >>= f` | flatMap | | `FlatMap[F[_]]` | `flatMap(fa: F[A])(f: A => F[B]): F[B]` |
| `fa >> fb` | followed by | | `FlatMap[F[_]]` | `followedBy(fa: F[A])(fb: F[B]): F[B]` |
| <code>x &#124;-&#124; y</code> | remove | | `Group[A]` | `remove(x: A, y: A): A` |
| `x > y` | greater than | | `PartialOrder[A]` | `gt(x: A, y: A): Boolean` |
| `x >= y` | greater than or equal | | `PartialOrder[A]` | `gteq(x: A, y: A): Boolean` |
| `x < y` | less than | | `PartialOrder[A]` | `lt(x: A, y: A): Boolean` |
| `x <= y` | less than or equal | | `PartialOrder[A]` | `lteq(x: A, y: A): Boolean` |
| <code>x &#124;+&#124; y</code> | Semigroup combine | | `Semigroup[A]` | `combine(x: A, y: A): A` |
| `x <+> y` | SemigroupK combine | | `SemigroupK[F[_]]` | `combineK(x: F[A], y: F[A]): F[A]` |
| `f <<< g` | Arrow compose | | `Compose[F[_, _]]` | `compose(f: F[B, C], g: F[A, B]): F[A, C]` |
| `f >>> g` | Arrow andThen | | `Compose[F[_, _]]` | `andThen(f: F[B, C], g: F[A, B]): F[A, C]` |
| `f &&& g` | Arrow merge | | `Arrow[F[_, _]]` | `merge[A, B, C](f: F[A, B], g: F[A, C]): F[A, (B, C)]` |
| `f -< g` | Arrow combine and bypass | | `Arrow[F[_, _]]` | `combineAndByPass[A, B, C](f: F[A, B], g: F[B, C]): F[A, (B, C)]` |
| `F ~> G` | natural transformation | | `FunctionK[F[_], G[_]]` | `FunctionK` alias |
| `F :<: G` | injectK | | `InjectK[F[_], G[_]]` | `InjectK` alias |
| `F :≺: G` | injectK | | `InjectK[F[_], G[_]]` | `InjectK` alias |
| `fa &> fb` | parallel followed by | | `Parallel[M[_], F[_]]` | `parFollowedBy[A, B](ma: M[A])(mb: M[B]): M[B]` |
| `fa <& fb` | parallel for effect | | `Parallel[M[_], F[_]]` | `parForEffect[A, B](ma: M[A])(mb: M[B]): M[A]` |
| `⊥` | bottom | | N/A | `Nothing` |
| `⊤` | top | | N/A | `Any` |
| `fa << fb` (Deprecated) | for effect | | `FlatMap[F[_]]` | `forEffect(fa: F[A])(fb: F[B]): F[A]` |
| Symbol | Name | Nickname | Type Class | Signature |
| -------------------------------- | -------------------------| ---------------- | ----------------------- | -----------------------------------------------------------------------------|
| `fa *> fb` | followed by | | `Apply[F[_]]` | `followedBy(fa: F[A])(fb: F[B]): F[B]` |
| `fa <* fb` | for effect | | `Apply[F[_]]` | `forEffect(fa: F[A])(fb: F[B]): F[A]` |
| `x === y` | equals | | `Eq[A]` | `eqv(x: A, y: A): Boolean` |
| `x =!= y` | not equals | | `Eq[A]` | `neqv(x: A, y: A): Boolean` |
| `fa >>= f` | flatMap | | `FlatMap[F[_]]` | `flatMap(fa: F[A])(f: A => F[B]): F[B]` |
| `fa >> fb` | followed by | | `FlatMap[F[_]]` | `followedBy(fa: F[A])(fb: F[B]): F[B]` |
| <code>x &#124;-&#124; y</code> | remove | | `Group[A]` | `remove(x: A, y: A): A` |
| `x > y` | greater than | | `PartialOrder[A]` | `gt(x: A, y: A): Boolean` |
| `x >= y` | greater than or equal | | `PartialOrder[A]` | `gteq(x: A, y: A): Boolean` |
| `x < y` | less than | | `PartialOrder[A]` | `lt(x: A, y: A): Boolean` |
| `x <= y` | less than or equal | | `PartialOrder[A]` | `lteq(x: A, y: A): Boolean` |
| <code>x &#124;+&#124; y</code> | Semigroup combine | | `Semigroup[A]` | `combine(x: A, y: A): A` |
| `x <+> y` | SemigroupK combine | | `SemigroupK[F[_]]` | `combineK(x: F[A], y: F[A]): F[A]` |
| `f <<< g` | Arrow compose | | `Compose[F[_, _]]` | `compose(f: F[B, C], g: F[A, B]): F[A, C]` |
| `f >>> g` | Arrow andThen | | `Compose[F[_, _]]` | `andThen(f: F[B, C], g: F[A, B]): F[A, C]` |
| `f &&& g` | Arrow merge | | `Arrow[F[_, _]]` | `merge[A, B, C](f: F[A, B], g: F[A, C]): F[A, (B, C)]` |
| `f -< g` | Arrow combine and bypass | | `Arrow[F[_, _]]` | `combineAndByPass[A, B, C](f: F[A, B], g: F[B, C]): F[A, (B, C)]` |
| `f +++ g` | ArrowChoice choose | | `ArrowChoice[F[_, _]]` | `choose[A, B, C, D](f: F[A, C])(g: F[B, D]): F[Either [A, B], Either[C, D]]` |
| `f ||| g` | Choice choice | | `Choice[F[_, _]]` | `choice[A, B, C](f: F[A, C], g: F[B, C]): F[Either[A, B], C]` |
| `F ~> G` | natural transformation | | `FunctionK[F[_], G[_]]` | `FunctionK` alias |
| `F :<: G` | injectK | | `InjectK[F[_], G[_]]` | `InjectK` alias |
| `F :≺: G` | injectK | | `InjectK[F[_], G[_]]` | `InjectK` alias |
| `fa &> fb` | parallel followed by | | `Parallel[M[_], F[_]]` | `parFollowedBy[A, B](ma: M[A])(mb: M[B]): M[B]` |
| `fa <& fb` | parallel for effect | | `Parallel[M[_], F[_]]` | `parForEffect[A, B](ma: M[A])(mb: M[B]): M[A]` |
| `⊥` | bottom | | N/A | `Nothing` |
| `⊤` | top | | N/A | `Any` |
| `fa << fb` (Deprecated) | for effect | | `FlatMap[F[_]]` | `forEffect(fa: F[A])(fb: F[B]): F[A]` |

## <a id="law-testing" href="#law-testing"></a>How can I test instances against their type classes' laws?

Expand Down
Loading