Skip to content

Commit

Permalink
Replace Split with Commutative Arrow. Continuing #1719 (#1766)
Browse files Browse the repository at this point in the history
* Cats-1567 Replace Split with Commutative Arrow

This commit removes from the `core` module the `Split` typeclass. This
includes the following changes:

* The `Arrow` typeclass is no longer a subtype of `Split`.
* The `split` operation, previously in the `Split` typeclass, is now
  integrated in the `Arrow` typeclass, with a default implementation.
* Following notation from the  `Control.Arrow` library for Haskell,
  we use the operator `***` as an alias for `split`.
* `SplitSyntax` is replaced with `ArrowSyntax`.
* We remove the `SplitLaws` and the `SplitTests`. In consequence,
  ArrowLaws does not inherit from SplitLaws anymore.
* We modify the instances for `Kleisli`. We remove the trait `KleisliSpli`,
  and we replace the _factory_ method that was generating it to generate
  a `KleisliCompose` instead.
* We remove the tests on the `SplitLaws` for Kleisli and CoKleisli

* Cats-1567 Add Commutative Arrows

We add a type-class of commutative arrows, which are those in which
the `split` operation is commutative (can pair in any order).

* Cats-1567 Introduce Commutative Monad

We introduce a Commutative Monad Type-class, a subclass of Monad in
which the flatMap of independent operations is commutative.

* Cats-1567 Kleisli Instances

We introduce some instances of CommutativeArrow for Kleisli, which
are based on CommutativeMonad.

* Cats-1567 Split CommutativeMonad into Commutative FlatMap

We introduce a new type class, CommutativeFlatMap, to load the
commutativity law one level up.

* Cats-1567 Commutative Comonad - CoflatMap

We introduce the duals of Commutative Comonads and CoflatMap,
as the duals of commutative Flatmaps and Monads.
This is done to generate and test CommutativeArrow instances for the
Cokleisli datatype.

* 1567 Complete tests for Cokleisli

We complete the tests for the CommutativeArrow instance of Cokleisli.
To use it, we mark the Monad instance of `NonEmptyList` as a
Commutative Monad.

* NonEmptyList comonad is not commutative

* fix unmoored statement

* added CommutativeMonad instances for Kleisli and WriterT

* fix import error

* made function1 commutativeArrow

* removed CommutativeComonad and CommutativeCoflatmap

* added back arrow tests
  • Loading branch information
kailuowang authored Jul 19, 2017
1 parent 27a7399 commit 16ea2ed
Show file tree
Hide file tree
Showing 33 changed files with 415 additions and 189 deletions.
14 changes: 14 additions & 0 deletions core/src/main/scala/cats/CommutativeFlatMap.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package cats

import simulacrum.typeclass

/**
* Commutative FlatMap.
*
* Further than a FlatMap, which just allows composition of dependent effectful functions,
* in a Commutative FlatMap those functions can be composed in any order, which guarantees
* that their effects do not interfere.
*
* Must obey the laws defined in cats.laws.CommutativeFlatMapLaws.
*/
@typeclass trait CommutativeFlatMap[F[_]] extends FlatMap[F]
14 changes: 14 additions & 0 deletions core/src/main/scala/cats/CommutativeMonad.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package cats

import simulacrum.typeclass

/**
* Commutative Monad.
*
* Further than a Monad, which just allows composition of dependent effectful functions,
* in a Commutative Monad those functions can be composed in any order, which guarantees
* that their effects do not interfere.
*
* Must obey the laws defined in cats.laws.CommutativeMonadLaws.
*/
@typeclass trait CommutativeMonad[F[_]] extends Monad[F] with CommutativeFlatMap[F]
31 changes: 28 additions & 3 deletions core/src/main/scala/cats/arrow/Arrow.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,16 @@ import cats.functor.Strong

import simulacrum.typeclass

@typeclass trait Arrow[F[_, _]] extends Split[F] with Strong[F] with Category[F] { self =>
/**
* Must obey the laws defined in cats.laws.ArrowLaws.
*/
@typeclass trait Arrow[F[_, _]] extends Category[F] with Strong[F] { self =>

/**
* Lift a function into the context of an Arrow
* Lift a function into the context of an Arrow.
*
* In the reference articles "Arrows are Promiscuous...", and in the corresponding Haskell
* library `Control.Arrow`, this function is called `arr`.
*/
def lift[A, B](f: A => B): F[A, B]

Expand All @@ -20,6 +26,25 @@ import simulacrum.typeclass
compose(swap, compose(first[A, B, C](fa), swap))
}

override def split[A, B, C, D](f: F[A, B], g: F[C, D]): F[(A, C), (B, D)] =
/**
* Create a new computation `F` that splits its input between `f` and `g`
* and combines the output of each.
*
* Example:
* {{{
* scala> import cats.implicits._
* scala> import cats.arrow.Arrow
* scala> val toLong: Int => Long = _.toLong
* scala> val toDouble: Float => Double = _.toDouble
* scala> val f: ((Int, Float)) => (Long, Double) = Arrow[Function1].split(toLong, toDouble)
* scala> f((3, 4.0f))
* res0: (Long, Double) = (3,4.0)
* }}}
*
* Note that the arrow laws do not guarantee the non-interference between the _effects_ of
* `f` and `g` in the context of F. This means that `f *** g` may not be equivalent to `g *** f`.
*/
@simulacrum.op("***", alias = true)
def split[A, B, C, D](f: F[A, B], g: F[C, D]): F[(A, C), (B, D)] =
andThen(first(f), second(g))
}
13 changes: 13 additions & 0 deletions core/src/main/scala/cats/arrow/CommutativeArrow.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package cats
package arrow

import simulacrum.typeclass

/**
* In a Commutative Arrow F[_, _], the split operation (or `***`) is commutative,
* which means that there is non-interference between the effect of the paired arrows.
*
* Must obey the laws in CommutativeArrowLaws
*/
@typeclass trait CommutativeArrow[F[_, _]] extends Arrow[F]

24 changes: 0 additions & 24 deletions core/src/main/scala/cats/arrow/Split.scala

This file was deleted.

74 changes: 43 additions & 31 deletions core/src/main/scala/cats/data/Cokleisli.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package cats
package data

import cats.arrow.{Arrow, Category, Compose, Split}
import cats.arrow.{Arrow, Category, CommutativeArrow, Compose}
import cats.functor.{Contravariant, Profunctor}
import cats.{CoflatMap, Comonad, Functor, Monad}
import scala.annotation.tailrec
Expand Down Expand Up @@ -45,37 +45,27 @@ object Cokleisli extends CokleisliInstances {
}

private[data] sealed abstract class CokleisliInstances extends CokleisliInstances0 {
implicit def catsDataArrowForCokleisli[F[_]](implicit ev: Comonad[F]): Arrow[Cokleisli[F, ?, ?]] =
new CokleisliArrow[F] { def F: Comonad[F] = ev }

implicit def catsDataMonadForCokleisli[F[_], A]: Monad[Cokleisli[F, A, ?]] = new Monad[Cokleisli[F, A, ?]] {
def pure[B](x: B): Cokleisli[F, A, B] =
Cokleisli.pure(x)

def flatMap[B, C](fa: Cokleisli[F, A, B])(f: B => Cokleisli[F, A, C]): Cokleisli[F, A, C] =
fa.flatMap(f)

override def map[B, C](fa: Cokleisli[F, A, B])(f: B => C): Cokleisli[F, A, C] =
fa.map(f)

def tailRecM[B, C](b: B)(fn: B => Cokleisli[F, A, Either[B, C]]): Cokleisli[F, A, C] =
Cokleisli({ (fa: F[A]) =>
@tailrec
def loop(c: Cokleisli[F, A, Either[B, C]]): C = c.run(fa) match {
case Right(c) => c
case Left(bb) => loop(fn(bb))
}
loop(fn(b))
})
implicit val catsDataCommutativeArrowForCokleisliId: CommutativeArrow[Cokleisli[Id, ?, ?]] =
new CokleisliArrow[Id] with CommutativeArrow[Cokleisli[Id, ?, ?]] {
def F: Comonad[Id] = Comonad[Id]
}

implicit def catsDataMonadForCokleisli[F[_], A]: Monad[Cokleisli[F, A, ?]] =
new CokleisliMonad[F, A]

implicit def catsDataMonoidKForCokleisli[F[_]](implicit ev: Comonad[F]): MonoidK[λ[α => Cokleisli[F, α, α]]] =
Category[Cokleisli[F, ?, ?]].algebraK
}

private[data] sealed abstract class CokleisliInstances0 {
implicit def catsDataSplitForCokleisli[F[_]](implicit ev: CoflatMap[F]): Split[Cokleisli[F, ?, ?]] =
new CokleisliSplit[F] { def F: CoflatMap[F] = ev }
private[data] sealed abstract class CokleisliInstances0 extends CokleisliInstances1 {
implicit def catsDataArrowForCokleisli[F[_]](implicit ev: Comonad[F]): Arrow[Cokleisli[F, ?, ?]] =
new CokleisliArrow[F] { def F: Comonad[F] = ev }
}

private[data] sealed abstract class CokleisliInstances1 {
implicit def catsDataComposeForCokleisli[F[_]](implicit ev: CoflatMap[F]): Compose[Cokleisli[F, ?, ?]] =
new CokleisliCompose[F] { def F: CoflatMap[F] = ev }

implicit def catsDataProfunctorForCokleisli[F[_]](implicit ev: Functor[F]): Profunctor[Cokleisli[F, ?, ?]] =
new CokleisliProfunctor[F] { def F: Functor[F] = ev }
Expand All @@ -89,7 +79,32 @@ private[data] sealed abstract class CokleisliInstances0 {
}
}

private trait CokleisliArrow[F[_]] extends Arrow[Cokleisli[F, ?, ?]] with CokleisliSplit[F] with CokleisliProfunctor[F] {


private[data] class CokleisliMonad[F[_], A] extends Monad[Cokleisli[F, A, ?]] {

def pure[B](x: B): Cokleisli[F, A, B] =
Cokleisli.pure(x)

def flatMap[B, C](fa: Cokleisli[F, A, B])(f: B => Cokleisli[F, A, C]): Cokleisli[F, A, C] =
fa.flatMap(f)

override def map[B, C](fa: Cokleisli[F, A, B])(f: B => C): Cokleisli[F, A, C] =
fa.map(f)

def tailRecM[B, C](b: B)(fn: B => Cokleisli[F, A, Either[B, C]]): Cokleisli[F, A, C] =
Cokleisli({ (fa: F[A]) =>
@tailrec
def loop(c: Cokleisli[F, A, Either[B, C]]): C = c.run(fa) match {
case Right(c) => c
case Left(bb) => loop(fn(bb))
}
loop(fn(b))
})

}

private trait CokleisliArrow[F[_]] extends Arrow[Cokleisli[F, ?, ?]] with CokleisliCompose[F] with CokleisliProfunctor[F] {
implicit def F: Comonad[F]

def lift[A, B](f: A => B): Cokleisli[F, A, B] =
Expand All @@ -108,17 +123,14 @@ private trait CokleisliArrow[F[_]] extends Arrow[Cokleisli[F, ?, ?]] with Coklei
super[CokleisliProfunctor].dimap(fab)(f)(g)

override def split[A, B, C, D](f: Cokleisli[F, A, B], g: Cokleisli[F, C, D]): Cokleisli[F, (A, C), (B, D)] =
super[CokleisliSplit].split(f, g)
Cokleisli(fac => f.run(F.map(fac)(_._1)) -> g.run(F.map(fac)(_._2)))
}

private trait CokleisliSplit[F[_]] extends Split[Cokleisli[F, ?, ?]] {
private trait CokleisliCompose[F[_]] extends Compose[Cokleisli[F, ?, ?]] {
implicit def F: CoflatMap[F]

def compose[A, B, C](f: Cokleisli[F, B, C], g: Cokleisli[F, A, B]): Cokleisli[F, A, C] =
f.compose(g)

def split[A, B, C, D](f: Cokleisli[F, A, B], g: Cokleisli[F, C, D]): Cokleisli[F, (A, C), (B, D)] =
Cokleisli(fac => f.run(F.map(fac)(_._1)) -> g.run(F.map(fac)(_._2)))
}

private trait CokleisliProfunctor[F[_]] extends Profunctor[Cokleisli[F, ?, ?]] {
Expand Down
45 changes: 27 additions & 18 deletions core/src/main/scala/cats/data/Kleisli.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package cats
package data

import cats.arrow.{Arrow, Category, Choice, Compose, Split, FunctionK}
import cats.arrow.{Arrow, Category, Choice, CommutativeArrow, Compose, FunctionK}
import cats.functor.{Contravariant, Strong}

/**
Expand Down Expand Up @@ -81,7 +81,13 @@ private[data] sealed trait KleisliFunctions {
}

private[data] sealed abstract class KleisliInstances extends KleisliInstances0 {
implicit def catsDataCommutativeMonadForKleisli[F[_], A, B](implicit F0: CommutativeMonad[F]): CommutativeMonad[Kleisli[F, A, ?]] =
new KleisliMonad[F, A] with CommutativeMonad[Kleisli[F, A, ?]] {
implicit def F: Monad[F] = F0
}
}

private[data] sealed abstract class KleisliInstances0 extends KleisliInstances1 {
implicit def catsDataMonoidForKleisli[F[_], A, B](implicit FB0: Monoid[F[B]]): Monoid[Kleisli[F, A, B]] =
new KleisliMonoid[F, A, B] { def FB: Monoid[F[B]] = FB0 }

Expand All @@ -91,11 +97,11 @@ private[data] sealed abstract class KleisliInstances extends KleisliInstances0 {
implicit val catsDataMonoidKForKleisliId: MonoidK[λ[α => Kleisli[Id, α, α]]] =
catsDataMonoidKForKleisli[Id]

implicit def catsDataArrowForKleisli[F[_]](implicit M: Monad[F]): Arrow[Kleisli[F, ?, ?]] =
new KleisliArrow[F] { def F: Monad[F] = M }
implicit def catsDataCommutativeArrowForKleisli[F[_]](implicit M: CommutativeMonad[F]): CommutativeArrow[Kleisli[F, ?, ?]] =
new KleisliCommutativeArrow[F] {def F: CommutativeMonad[F] = M }

implicit val catsDataArrowForKleisliId: Arrow[Kleisli[Id, ?, ?]] =
catsDataArrowForKleisli[Id]
implicit val catsDataCommutativeArrowForKleisliId: CommutativeArrow[Kleisli[Id, ?, ?]] =
catsDataCommutativeArrowForKleisli[Id]

implicit def catsDataMonadReaderForKleisliId[A]: MonadReader[Kleisli[Id, A, ?], A] =
catsDataMonadReaderForKleisli[Id, A]
Expand All @@ -115,25 +121,28 @@ private[data] sealed abstract class KleisliInstances extends KleisliInstances0 {
new KleisliApplicativeError[F, A, E] { def F: ApplicativeError[F, E] = AE }
}

private[data] sealed abstract class KleisliInstances0 extends KleisliInstances1 {
private[data] sealed abstract class KleisliInstances1 extends KleisliInstances2 {
implicit def catsDataArrowForKleisli[F[_]](implicit M: Monad[F]): Arrow[Kleisli[F, ?, ?]] =
new KleisliArrow[F] { def F: Monad[F] = M }

implicit def catsDataMonadErrorForKleisli[F[_], A, E](implicit ME: MonadError[F, E]): MonadError[Kleisli[F, A, ?], E] =
new KleisliMonadError[F, A, E] { def F: MonadError[F, E] = ME }
}

private[data] sealed abstract class KleisliInstances1 extends KleisliInstances2 {
private[data] sealed abstract class KleisliInstances2 extends KleisliInstances3 {
implicit def catsDataMonadReaderForKleisli[F[_], A](implicit M: Monad[F]): MonadReader[Kleisli[F, A, ?], A] =
new KleisliMonadReader[F, A] { def F: Monad[F] = M }
}

private[data] sealed abstract class KleisliInstances2 extends KleisliInstances3 {
private[data] sealed abstract class KleisliInstances3 extends KleisliInstances4 {
implicit def catsDataChoiceForKleisli[F[_]](implicit M: Monad[F]): Choice[Kleisli[F, ?, ?]] =
new KleisliChoice[F] { def F: Monad[F] = M }

implicit val catsDataChoiceForKleisliId: Choice[Kleisli[Id, ?, ?]] =
catsDataChoiceForKleisli[Id]

implicit def catsDataSplitForKleisli[F[_]](implicit FM: FlatMap[F]): Split[Kleisli[F, ?, ?]] =
new KleisliSplit[F] { def F: FlatMap[F] = FM }
implicit def catsDataComposeForKleisli[F[_]](implicit FM: FlatMap[F]): Compose[Kleisli[F, ?, ?]] =
new KleisliCompose[F] { def F: FlatMap[F] = FM }

implicit def catsDataStrongForKleisli[F[_]](implicit F0: Functor[F]): Strong[Kleisli[F, ?, ?]] =
new KleisliStrong[F] { def F: Functor[F] = F0 }
Expand All @@ -148,30 +157,30 @@ private[data] sealed abstract class KleisliInstances2 extends KleisliInstances3
Compose[Kleisli[F, ?, ?]].algebraK
}

private[data] sealed abstract class KleisliInstances3 extends KleisliInstances4 {
private[data] sealed abstract class KleisliInstances4 extends KleisliInstances5 {
implicit def catsDataApplicativeForKleisli[F[_], A](implicit A: Applicative[F]): Applicative[Kleisli[F, A, ?]] =
new KleisliApplicative[F, A] { def F: Applicative[F] = A }
}

private[data] sealed abstract class KleisliInstances4 extends KleisliInstances5 {
private[data] sealed abstract class KleisliInstances5 extends KleisliInstances6 {
implicit def catsDataApplyForKleisli[F[_], A](implicit A: Apply[F]): Apply[Kleisli[F, A, ?]] =
new KleisliApply[F, A] { def F: Apply[F] = A }
}

private[data] sealed abstract class KleisliInstances5 {
private[data] sealed abstract class KleisliInstances6 {
implicit def catsDataFunctorForKleisli[F[_], A](implicit F0: Functor[F]): Functor[Kleisli[F, A, ?]] =
new KleisliFunctor[F, A] { def F: Functor[F] = F0 }
}

private trait KleisliArrow[F[_]] extends Arrow[Kleisli[F, ?, ?]] with KleisliSplit[F] with KleisliStrong[F] with KleisliCategory[F] {
private trait KleisliCommutativeArrow[F[_]] extends CommutativeArrow[Kleisli[F, ?, ?]] with KleisliArrow[F] {
implicit def F: CommutativeMonad[F]
}

private trait KleisliArrow[F[_]] extends Arrow[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)))
}

private trait KleisliSplit[F[_]] extends Split[Kleisli[F, ?, ?]] with KleisliCompose[F] {
implicit def F: FlatMap[F]

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))) }
Expand Down
Loading

0 comments on commit 16ea2ed

Please sign in to comment.