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

Cats-1567 Replace Split with Commutative Arrow. Introduces Commutative Monad. #1719

Closed
wants to merge 7 commits into from
Closed
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
14 changes: 14 additions & 0 deletions core/src/main/scala/cats/CommutativeCoflatMap.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package cats

import simulacrum.typeclass

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

import simulacrum.typeclass

/**
* Commutative Comonad.
*
* Further than a Comonad, which just allows composition of dependent effectful functions,
* in a Commutative Comonad those functions can be composed in any order, which guarantees
* that their effects do not interfere.
*
* Must obey the laws defined in cats.laws.CommutativeComonadLaws.
*/
@typeclass trait CommutativeComonad[F[_]] extends Comonad[F] with CommutativeCoflatMap[F]
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))
Copy link
Contributor

Choose a reason for hiding this comment

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

quick note, codecov somehow mistakenly thinks this line is uncovered.

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

78 changes: 46 additions & 32 deletions core/src/main/scala/cats/data/Cokleisli.scala
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
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 cats.{CoflatMap, Comonad, CommutativeComonad, 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)
implicit def catsDataCommutativeArrowForCokleisli[F[_]](implicit ev: CommutativeComonad[F]): CommutativeArrow[Cokleisli[F, ?, ?]] =
new CokleisliCommutativeArrow[F] { def F: CommutativeComonad[F] = ev }
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like that the build wasn't happy because you forgot to give this extra help scalac needed.

  implicit val catsDataCommutativeArrowForCokleisliId: CommutativeArrow[Cokleisli[Id, ?, ?]] =
    catsDataCommutativeArrowForCokleisli[Id]


def flatMap[B, C](fa: Cokleisli[F, A, B])(f: B => Cokleisli[F, A, C]): Cokleisli[F, A, C] =
fa.flatMap(f)
implicit val catsDataCommutativeArrowForCokleisliId: CommutativeArrow[Cokleisli[Id, ?, ?]] =
catsDataCommutativeArrowForCokleisli[Id]

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 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,34 @@ private[data] sealed abstract class CokleisliInstances0 {
}
}

private trait CokleisliArrow[F[_]] extends Arrow[Cokleisli[F, ?, ?]] with CokleisliSplit[F] with CokleisliProfunctor[F] {
private[data] trait CokleisliCommutativeArrow[F[_]] extends CommutativeArrow[Cokleisli[F, ?, ?]] with CokleisliArrow[F] {
implicit def F: CommutativeComonad[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 +125,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
27 changes: 15 additions & 12 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 @@ -91,11 +91,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 @@ -116,6 +116,9 @@ private[data] sealed abstract class KleisliInstances extends KleisliInstances0 {
}

private[data] sealed abstract class KleisliInstances0 extends KleisliInstances1 {
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 }
}
Expand All @@ -132,8 +135,8 @@ private[data] sealed abstract class KleisliInstances2 extends KleisliInstances3
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 Down Expand Up @@ -163,15 +166,15 @@ private[data] sealed abstract class KleisliInstances5 {
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))) }
Copy link
Contributor

Choose a reason for hiding this comment

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

same situation here as in Cokleisli.
An alternative approach we can take is to add split to the arrowLaws that simply tests whatever override is consistent with andThen(first(f), second(g)). Unfortunately, we don't have a consistent strategy over how to tests these overrides in instances yet. So for now, I'd vote for just adding a doctest.

Expand Down
4 changes: 2 additions & 2 deletions core/src/main/scala/cats/data/NonEmptyList.scala
Original file line number Diff line number Diff line change
Expand Up @@ -367,8 +367,8 @@ object NonEmptyList extends NonEmptyListInstances {
private[data] sealed trait NonEmptyListInstances extends NonEmptyListInstances0 {

implicit val catsDataInstancesForNonEmptyList: SemigroupK[NonEmptyList] with Reducible[NonEmptyList]
with Comonad[NonEmptyList] with NonEmptyTraverse[NonEmptyList] with Monad[NonEmptyList] =
new NonEmptyReducible[NonEmptyList, List] with SemigroupK[NonEmptyList] with Comonad[NonEmptyList]
with CommutativeComonad[NonEmptyList] with NonEmptyTraverse[NonEmptyList] with Monad[NonEmptyList] =
new NonEmptyReducible[NonEmptyList, List] with SemigroupK[NonEmptyList] with CommutativeComonad[NonEmptyList]
with Monad[NonEmptyList] with NonEmptyTraverse[NonEmptyList] {

def combineK[A](a: NonEmptyList[A], b: NonEmptyList[A]): NonEmptyList[A] =
Expand Down
4 changes: 2 additions & 2 deletions core/src/main/scala/cats/instances/option.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import scala.annotation.tailrec

trait OptionInstances extends cats.kernel.instances.OptionInstances {

implicit val catsStdInstancesForOption: TraverseFilter[Option] with MonadError[Option, Unit] with MonadCombine[Option] with Monad[Option] with CoflatMap[Option] with Alternative[Option] =
new TraverseFilter[Option] with MonadError[Option, Unit] with MonadCombine[Option] with Monad[Option] with CoflatMap[Option] with Alternative[Option] {
implicit val catsStdInstancesForOption: TraverseFilter[Option] with MonadError[Option, Unit] with MonadCombine[Option] with CommutativeMonad[Option] with CoflatMap[Option] with Alternative[Option] =
new TraverseFilter[Option] with MonadError[Option, Unit] with MonadCombine[Option] with CommutativeMonad[Option] with CoflatMap[Option] with Alternative[Option] {

def empty[A]: Option[A] = None

Expand Down
4 changes: 2 additions & 2 deletions core/src/main/scala/cats/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ package object cats {
* encodes pure unary function application.
*/
type Id[A] = A
implicit val catsInstancesForId: Bimonad[Id] with Monad[Id] with NonEmptyTraverse[Id] =
new Bimonad[Id] with Monad[Id] with NonEmptyTraverse[Id] {
implicit val catsInstancesForId: Bimonad[Id] with CommutativeMonad[Id] with CommutativeComonad[Id] with NonEmptyTraverse[Id] =
new Bimonad[Id] with CommutativeMonad[Id] with CommutativeComonad[Id] with NonEmptyTraverse[Id] {
def pure[A](a: A): A = a
def extract[A](a: A): A = a
def flatMap[A, B](a: A)(f: A => B): B = f(a)
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/scala/cats/syntax/all.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ trait AllSyntax
extends ApplicativeSyntax
with ApplicativeErrorSyntax
with ApplySyntax
with ArrowSyntax
with BifunctorSyntax
with BifoldableSyntax
with BitraverseSyntax
Expand Down Expand Up @@ -38,7 +39,6 @@ trait AllSyntax
with SemigroupSyntax
with SemigroupKSyntax
with ShowSyntax
with SplitSyntax
with StrongSyntax
with TraverseFilterSyntax
with TraverseSyntax
Expand Down
6 changes: 6 additions & 0 deletions core/src/main/scala/cats/syntax/arrow.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package cats
package syntax

import cats.arrow.Arrow

trait ArrowSyntax extends Arrow.ToArrowOps
Loading