diff --git a/core/src/main/scala/cats/Alternative.scala b/core/src/main/scala/cats/Alternative.scala index 41a095f703..6fe269833a 100644 --- a/core/src/main/scala/cats/Alternative.scala +++ b/core/src/main/scala/cats/Alternative.scala @@ -2,5 +2,20 @@ package cats import simulacrum.typeclass -@typeclass trait Alternative[F[_]] extends Applicative[F] with MonoidK[F] +@typeclass trait Alternative[F[_]] extends Applicative[F] with MonoidK[F] { self => + /** + * Compose two Alternative intsances. + */ + override def compose[G[_]](implicit GG: Applicative[G]): Alternative[λ[α => F[G[α]]]] = + new CompositeAlternative[F, G] { + implicit def F: Alternative[F] = self + implicit def G: Applicative[G] = GG + } +} +trait CompositeAlternative[F[_], G[_]] + extends Alternative[λ[α => F[G[α]]]] with CompositeApplicative[F, G] with CompositeMonoidK[F, G] { + + implicit def F: Alternative[F] + implicit def G: Applicative[G] +} diff --git a/core/src/main/scala/cats/MonoidK.scala b/core/src/main/scala/cats/MonoidK.scala index b43e23c968..ecbd251b68 100644 --- a/core/src/main/scala/cats/MonoidK.scala +++ b/core/src/main/scala/cats/MonoidK.scala @@ -29,6 +29,19 @@ import simulacrum.typeclass */ def empty[A]: F[A] + /** + * Compose this MonoidK with an arbitrary type constructor + */ + override def composedWith[G[_]]: MonoidK[λ[α => F[G[α]]]] = + new CompositeMonoidK[F, G] { + implicit def F: MonoidK[F] = self + } + + /** + * Compose two MonoidK instances. + */ + def compose[G[_]](implicit GG: MonoidK[G]): MonoidK[λ[α => F[G[α]]]] = composedWith[G] + /** * Given a type A, create a concrete Monoid[F[A]]. */ @@ -38,3 +51,11 @@ import simulacrum.typeclass def combine(x: F[A], y: F[A]): F[A] = self.combine(x, y) } } + +trait CompositeMonoidK[F[_],G[_]] + extends MonoidK[λ[α => F[G[α]]]] with CompositeSemigroupK[F, G] { + + implicit def F: MonoidK[F] + + def empty[A]: F[G[A]] = F.empty +} diff --git a/core/src/main/scala/cats/SemigroupK.scala b/core/src/main/scala/cats/SemigroupK.scala index adfdb3504b..04e0e1c88a 100644 --- a/core/src/main/scala/cats/SemigroupK.scala +++ b/core/src/main/scala/cats/SemigroupK.scala @@ -29,13 +29,18 @@ import simulacrum.{op, typeclass} def combine[A](x: F[A], y: F[A]): F[A] /** - * Compose two SemigroupK intsances. + * Compose this SemigroupK with an arbitrary type constructor */ - def compose[G[_]: SemigroupK]: SemigroupK[λ[α => F[G[α]]]] = - new SemigroupK[λ[α => F[G[α]]]] { - def combine[A](x: F[G[A]], y: F[G[A]]): F[G[A]] = self.combine(x, y) + def composedWith[G[_]]: SemigroupK[λ[α => F[G[α]]]] = + new CompositeSemigroupK[F, G] { + implicit def F: SemigroupK[F] = self } + /** + * Compose two SemigroupK instances. + */ + def compose[G[_]](implicit GG: SemigroupK[G]): SemigroupK[λ[α => F[G[α]]]] = composedWith[G] + /** * Given a type A, create a concrete Semigroup[F[A]]. */ @@ -44,3 +49,11 @@ import simulacrum.{op, typeclass} def combine(x: F[A], y: F[A]): F[A] = self.combine(x, y) } } + +trait CompositeSemigroupK[F[_],G[_]] + extends SemigroupK[λ[α => F[G[α]]]] { + + implicit def F: SemigroupK[F] + + def combine[A](x: F[G[A]], y: F[G[A]]): F[G[A]] = F.combine(x, y) +} diff --git a/core/src/main/scala/cats/data/OneAnd.scala b/core/src/main/scala/cats/data/OneAnd.scala index 5500d15548..a0a470770b 100644 --- a/core/src/main/scala/cats/data/OneAnd.scala +++ b/core/src/main/scala/cats/data/OneAnd.scala @@ -161,6 +161,12 @@ trait OneAndLowPriority1 extends OneAndLowPriority0 { def map[A, B](fa: OneAnd[F, A])(f: A => B): OneAnd[F, B] = OneAnd(f(fa.head), F.map(fa.tail)(f)) } + + implicit def oneAndReducible[F[_]](implicit foldable: Foldable[F]): Reducible[OneAnd[F, ?]] = + new NonEmptyReducible[OneAnd[F, ?], F] { + def split[A](fa: OneAnd[F, A]): (A, F[A]) = (fa.head, fa.tail) + } + } object OneAnd extends OneAndInstances diff --git a/tests/src/test/scala/cats/tests/ComposeTests.scala b/tests/src/test/scala/cats/tests/ComposeTests.scala index 5aca2b5628..230adefd45 100644 --- a/tests/src/test/scala/cats/tests/ComposeTests.scala +++ b/tests/src/test/scala/cats/tests/ComposeTests.scala @@ -2,7 +2,8 @@ package cats package tests import cats.data.{ NonEmptyList, NonEmptyVector, OneAnd } -import cats.laws.discipline.{ ApplicativeTests, FoldableTests, MonoidalTests, SemigroupKTests, arbitrary, eq }, arbitrary._, eq._ +import cats.laws.discipline.{ AlternativeTests, ApplicativeTests, FoldableTests, MonoidKTests, MonoidalTests, SemigroupKTests } +import cats.laws.discipline.{ arbitrary, eq }, arbitrary._, eq._ import org.scalacheck.Arbitrary class ComposeTests extends CatsSuite { @@ -12,6 +13,15 @@ class ComposeTests extends CatsSuite { implicit override val generatorDrivenConfig: PropertyCheckConfiguration = PropertyCheckConfig(maxSize = 5, minSuccessful = 20) + { + // Alternative composition + + implicit val alternativeListVector: Alternative[Lambda[A => List[Vector[A]]]] = Alternative[List] compose Alternative[Vector] + implicit val iso = MonoidalTests.Isomorphisms.invariant[Lambda[A => List[Vector[A]]]] + + checkAll("Alternative[Lambda[A => List[Vector[A]]]]", AlternativeTests[Lambda[A => List[Vector[A]]]].alternative[Int, Int, Int]) + } + { // Applicative composition @@ -30,19 +40,18 @@ class ComposeTests extends CatsSuite { } { - // Reducible composition + // MonoidK composition + + implicit val monoidKListVector: MonoidK[Lambda[A => List[Vector[A]]]] = MonoidK[List] compose MonoidK[Vector] - val nelReducible = - new NonEmptyReducible[NonEmptyList, List] { - def split[A](fa: NonEmptyList[A]): (A, List[A]) = (fa.head, fa.tail) - } + checkAll("MonoidK[Lambda[A => List[Vector[A]]]]", MonoidKTests[Lambda[A => List[Vector[A]]]].monoidK[Int]) + } - val nevReducible = - new NonEmptyReducible[NonEmptyVector, Vector] { - def split[A](fa: NonEmptyVector[A]): (A, Vector[A]) = (fa.head, fa.tail) - } + { + // Reducible composition - implicit val reducibleListVector: Reducible[Lambda[A => NonEmptyList[NonEmptyVector[A]]]] = nelReducible compose nevReducible + implicit val reducibleListVector: Reducible[Lambda[A => NonEmptyList[NonEmptyVector[A]]]] = + Reducible[NonEmptyList] compose Reducible[NonEmptyVector] // No Reducible-specific laws, so check the Foldable laws are satisfied checkAll("Reducible[Lambda[A => List[Vector[A]]]]", FoldableTests[Lambda[A => NonEmptyList[NonEmptyVector[A]]]].foldable[Int, Int])