Skip to content

Commit

Permalink
Add Arrow Choice (#2096)
Browse files Browse the repository at this point in the history
* ArrowChoice, laws, function instance

* Kleisli instances, mima exceptions, tests

* Update symbols table

* Consolidate instances, add |||

* Fix laws

* Clean up laws code

* Consistency law, match over fold
  • Loading branch information
stephen-lazaro authored and LukaJCB committed Dec 13, 2017
1 parent e27266a commit fca2014
Show file tree
Hide file tree
Showing 13 changed files with 254 additions and 48 deletions.
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)

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))
}
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

0 comments on commit fca2014

Please sign in to comment.