diff --git a/core/src/main/scala/cats/Representable.scala b/core/src/main/scala/cats/Representable.scala index 223f1c1154..4ec0c7efdc 100644 --- a/core/src/main/scala/cats/Representable.scala +++ b/core/src/main/scala/cats/Representable.scala @@ -93,6 +93,16 @@ private trait RepresentableBimonad[F[_], R] extends RepresentableMonad[F, R] wit R.index(fa)(M.empty) } +private trait RepresentableDistributive[F[_], R] extends Distributive[F] { + + def R: Representable.Aux[F, R] + + override def distribute[G[_], A, B](ga: G[A])(f: A => F[B])(implicit G: Functor[G]): F[G[B]] = + R.tabulate(r => G.map(ga)(a => R.index(f(a))(r))) + + override def map[A, B](fa: F[A])(f: A => B): F[B] = R.F.map(fa)(f) +} + object Representable { type Aux[F[_], R] = Representable[F] { type Representation = R } @@ -119,7 +129,7 @@ object Representable { } /** - * Derives a `Bimonad` instance for any `Representable` functor whos representation + * Derives a `Bimonad` instance for any `Representable` functor whose representation * has a `Monoid` instance. */ def bimonad[F[_], R](implicit Rep: Representable.Aux[F, R], Mon: Monoid[R]): Bimonad[F] = @@ -127,4 +137,12 @@ object Representable { override def R: Representable.Aux[F, R] = Rep override def M: Monoid[R] = Mon } + + /** + * Derives a `Distributive` instance for any `Representable` functor + */ + def distributive[F[_]](implicit Rep: Representable[F]): Distributive[F] = + new RepresentableDistributive[F, Rep.Representation] { + override def R: Aux[F, Rep.Representation] = Rep + } } diff --git a/tests/src/test/scala/cats/tests/RepresentableSuite.scala b/tests/src/test/scala/cats/tests/RepresentableSuite.scala index c114c2e006..002127a708 100644 --- a/tests/src/test/scala/cats/tests/RepresentableSuite.scala +++ b/tests/src/test/scala/cats/tests/RepresentableSuite.scala @@ -3,7 +3,14 @@ package cats.tests import cats.laws.discipline.SemigroupalTests.Isomorphisms import cats.laws.discipline.arbitrary._ import cats.laws.discipline.eq._ -import cats.laws.discipline.{BimonadTests, MiniInt, MonadTests, RepresentableTests, SerializableTests} +import cats.laws.discipline.{ + BimonadTests, + DistributiveTests, + MiniInt, + MonadTests, + RepresentableTests, + SerializableTests +} import cats.{Bimonad, Eq, Eval, Id, Representable} import org.scalacheck.Arbitrary import cats.data.Kleisli @@ -60,7 +67,7 @@ class RepresentableSuite extends CatsSuite { } { - //the monadInstance below made a conflict to resolve this one. + // the monadInstance below made a conflict to resolve this one. // TODO ceedubs is this needed? implicit val isoFun1: Isomorphisms[MiniInt => ?] = Isomorphisms.invariant[MiniInt => ?] @@ -68,6 +75,11 @@ class RepresentableSuite extends CatsSuite { checkAll("MiniInt => ?", MonadTests[MiniInt => ?].monad[String, String, String]) } + { + implicit val distributiveInstance = Representable.distributive[Pair] + checkAll("Pair[Int]", DistributiveTests[Pair].distributive[Int, Int, Int, Option, MiniInt => ?]) + } + // Syntax tests. If it compiles is "passes" { // Pair