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 distributive typeclass and some instances #2046

Merged
merged 14 commits into from
Dec 12, 2017
8 changes: 8 additions & 0 deletions core/src/main/scala/cats/Composed.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
package cats


private[cats] trait ComposedDistributive[F[_], G[_]] extends Distributive[λ[α => F[G[α]]]] with ComposedFunctor[F, G] { outer =>
def F: Distributive[F]
def G: Distributive[G]

override def distribute[H[_]: Functor, A, B](ha: H[A])(f: A => F[G[B]]): F[G[H[B]]] =
F.map(F.distribute(ha)(f))(G.cosequence(_))
}

private[cats] trait ComposedInvariant[F[_], G[_]] extends Invariant[λ[α => F[G[α]]]] { outer =>
def F: Invariant[F]
def G: Invariant[G]
Expand Down
22 changes: 22 additions & 0 deletions core/src/main/scala/cats/Distributive.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package cats
import simulacrum.typeclass

@typeclass trait Distributive[F[_]] extends Functor[F] { self =>

/**
* Given a function which returns a distributive `F`, apply that value across the structure G.
*/
def distribute[G[_]: Functor, A, B](ga: G[A])(f: A => F[B]): F[G[B]]

/**
* Given a Functor G which wraps some distributive F, distribute F across the G.
*/
def cosequence[G[_]: Functor, A](ga: G[F[A]]): F[G[A]] = distribute(ga)(identity)

// Distributive composes
def compose[G[_]](implicit G0: Distributive[G]): Distributive[λ[α => F[G[α]]]] =
new ComposedDistributive[F, G] {
implicit def F = self
implicit def G = G0
}
}
17 changes: 16 additions & 1 deletion core/src/main/scala/cats/data/Kleisli.scala
Original file line number Diff line number Diff line change
Expand Up @@ -208,11 +208,16 @@ private[data] sealed abstract class KleisliInstances6 extends KleisliInstances7
new KleisliApply[F, A] { def F: Apply[F] = A }
}

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

private[data] sealed abstract class KleisliInstances8 {
implicit def kleisliDistributive[F[_], R](implicit F0: Distributive[F]): Distributive[Kleisli[F, R, ?]] =
Copy link
Contributor

@kailuowang kailuowang Dec 11, 2017

Choose a reason for hiding this comment

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

The Cats convention is that instances that are more specific should have higher priority. So we should probably swap the Functor and Distributive instance here.
Also, according to naming convention of implicits, it should be catsDataDistributiveForKleisli

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok.

new KleisliDistributive[F, R] { implicit def F: Distributive[F] = F0 }
}

private[data] trait KleisliCommutativeArrow[F[_]] extends CommutativeArrow[Kleisli[F, ?, ?]] with KleisliArrow[F] {
implicit def F: CommutativeMonad[F]
}
Expand Down Expand Up @@ -347,3 +352,13 @@ private[data] trait KleisliFunctor[F[_], A] extends Functor[Kleisli[F, A, ?]] {
override def map[B, C](fa: Kleisli[F, A, B])(f: B => C): Kleisli[F, A, C] =
fa.map(f)
}

private trait KleisliDistributive[F[_], R] extends Distributive[Kleisli[F, R, ?]] {
implicit def F: Distributive[F]

override def distribute[G[_]: Functor, A, B](a: G[A])(f: A => Kleisli[F, R, B]): Kleisli[F, R, G[B]] =
Kleisli(r => F.distribute(a)(f(_) run r))


def map[A, B](fa: Kleisli[F, R, A])(f: A => B): Kleisli[F, R, B] = fa.map(f)
}
7 changes: 7 additions & 0 deletions core/src/main/scala/cats/data/Nested.scala
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,13 @@ private[data] trait NestedTraverse[F[_], G[_]] extends Traverse[Nested[F, G, ?]]
Applicative[H].map(FG.traverse(fga.value)(f))(Nested(_))
}

private[data] trait NestedDistributive[F[_], G[_]] extends Distributive[Nested[F, G, ?]] with NestedFunctor[F, G] {
def FG: Distributive[λ[α => F[G[α]]]]

def distribute[H[_]: Functor, A, B](ha: H[A])(f: A => Nested[F, G, B]): Nested[F, G, H[B]] =
Nested(FG.distribute(ha) { a => f(a).value })
}

private[data] trait NestedReducible[F[_], G[_]] extends Reducible[Nested[F, G, ?]] with NestedFoldable[F, G] {
def FG: Reducible[λ[α => F[G[α]]]]

Expand Down
10 changes: 9 additions & 1 deletion core/src/main/scala/cats/instances/function.scala
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ private[instances] sealed trait Function0Instances {
}
}

private[instances] sealed trait Function1Instances {
private[instances] sealed trait Function1Instances extends Function1Instances1 {
implicit def catsStdContravariantForFunction1[R]: Contravariant[? => R] =
new Contravariant[? => R] {
def contramap[T1, T0](fa: T1 => R)(f: T0 => T1): T0 => R =
Expand Down Expand Up @@ -89,3 +89,11 @@ private[instances] sealed trait Function1Instances {
implicit val catsStdMonoidKForFunction1: MonoidK[λ[α => Function1[α, α]]] =
Category[Function1].algebraK
}

trait Function1Instances1 {
implicit def distributive[T1]: Distributive[T1 => ?] = new Distributive[T1 => ?] {
def distribute[F[_]: Functor, A, B](fa: F[A])(f: A => (T1 => B)): T1 => F[B] = {t1 => Functor[F].map(fa)(a => f(a)(t1)) }

def map[A, B](fa: T1 => A)(f: A => B): T1 => B = {t1 => f(fa(t1))}
}
}
5 changes: 3 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 CommutativeMonad[Id] with Comonad[Id] with NonEmptyTraverse[Id] =
new Bimonad[Id] with CommutativeMonad[Id] with Comonad[Id] with NonEmptyTraverse[Id] {
implicit val catsInstancesForId: Bimonad[Id] with CommutativeMonad[Id] with Comonad[Id] with NonEmptyTraverse[Id] with Distributive[Id] =
new Bimonad[Id] with CommutativeMonad[Id] with Comonad[Id] with NonEmptyTraverse[Id] with Distributive[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 All @@ -42,6 +42,7 @@ package object cats {
case Left(a1) => tailRecM(a1)(f)
case Right(b) => b
}
override def distribute[F[_], A, B](fa: F[A])(f: A => B)(implicit F: Functor[F]): Id[F[B]] = F.map(fa)(f)
override def map[A, B](fa: A)(f: A => B): B = f(fa)
override def ap[A, B](ff: A => B)(fa: A): B = ff(fa)
override def flatten[A](ffa: A): A = ffa
Expand Down
14 changes: 14 additions & 0 deletions core/src/main/scala/cats/syntax/DistributiveSyntax.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package cats
package syntax

import cats.evidence.===

trait DistributiveSyntax extends Distributive.ToDistributiveOps {
implicit final def catsSyntaxDistributiveOps[F[_]: Functor, A](fa: F[A]): DistributiveOps[F, A] = new DistributiveOps[F, A](fa)
Copy link
Member

Choose a reason for hiding this comment

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

Maybe we should add another class for cosequence, we wouldn't need cats.evidence then:

final class CosequenceOps[F[_]: Functor, G[_]: Distributive, A](val fga: F[G[A]]) extends AnyVal {
  def cosequence: G[F[A]] = G.cosequence(fga)
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmmm, not sure I see a reason why that's better? The evidence constraint is on just the one method and it saves the extra class for syntax. Is there a reason it should be preferred?

Copy link
Member

Choose a reason for hiding this comment

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

Maybe you're right, disregard me! :D

}

// Add syntax to functor as part of importing distributive syntax.
final class DistributiveOps[F[_]: Functor, A](val fa: F[A]) {
Copy link
Member

Choose a reason for hiding this comment

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

Can we make this an AnyVal? :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, because the class has the Functor constraint.

Copy link
Member

Choose a reason for hiding this comment

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

Ah yes, you're right! We should move the Functor constraint to the method instead

def distribute[G[_], B](f: A => G[B])(implicit G: Distributive[G]): G[F[B]] = G.distribute(fa)(f)
def cosequence[G[_], B](implicit G: Distributive[G], ev: A === G[B]): G[F[B]] = G.cosequence(ev.substitute(fa))
}
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 @@ -15,6 +15,7 @@ trait AllSyntax
with ComonadSyntax
with ComposeSyntax
with ContravariantSyntax
with DistributiveSyntax
with EitherKSyntax
with EitherSyntax
with EqSyntax
Expand Down
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 @@ -13,6 +13,7 @@ package object syntax {
@deprecated("use cats.syntax.semigroupal instead", "1.0.0-RC1")
object cartesian extends SemigroupalSyntax
object coflatMap extends CoflatMapSyntax
object distributive extends DistributiveSyntax
object eitherK extends EitherKSyntax
object comonad extends ComonadSyntax
object compose extends ComposeSyntax
Expand Down
20 changes: 20 additions & 0 deletions laws/src/main/scala/cats/laws/DistributiveLaws.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package cats
package laws

import cats.Id
trait DistributiveLaws[F[_]] extends FunctorLaws[F] {
implicit override def F: Distributive[F]

def cosequenceIdentity[G[_], A](fa: F[A]): IsEq[F[A]] = {
F.cosequence[Id, A](fa) <-> fa
}

// TODO need laws for
// cosequence andThen cosequence == id
// composition
}

object DistributiveLaws {
def apply[F[_]](implicit ev: Distributive[F]): DistributiveLaws[F] =
new DistributiveLaws[F] { def F: Distributive[F] = ev }
}
12 changes: 12 additions & 0 deletions laws/src/main/scala/cats/laws/discipline/DistributiveTests.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package cats
package laws
package discipline

trait DistributiveTests[F[_]] extends FunctorTests[F] {
def laws: DistributiveLaws[F]
}

object DistributiveTests {
def apply[F[_]: Distributive]: DistributiveTests[F] =
new DistributiveTests[F] { def laws: DistributiveLaws[F] = DistributiveLaws[F] }
}