diff --git a/core/src/main/scala/cats/Alternative.scala b/core/src/main/scala/cats/Alternative.scala index 41a095f703..369bd0bbf8 100644 --- a/core/src/main/scala/cats/Alternative.scala +++ b/core/src/main/scala/cats/Alternative.scala @@ -2,5 +2,21 @@ 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 this `Alternative` instance with an [[Applicative]] instance. + */ + 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 e55077151d..6cdcdb7368 100644 --- a/core/src/main/scala/cats/MonoidK.scala +++ b/core/src/main/scala/cats/MonoidK.scala @@ -29,6 +29,14 @@ import simulacrum.typeclass */ def empty[A]: F[A] + /** + * Compose this MonoidK with an arbitrary type constructor + */ + override def composeK[G[_]]: MonoidK[λ[α => F[G[α]]]] = + new CompositeMonoidK[F, G] { + implicit def F: MonoidK[F] = self + } + /** * Given a type A, create a concrete Monoid[F[A]]. */ @@ -38,3 +46,11 @@ import simulacrum.typeclass def combine(x: F[A], y: F[A]): F[A] = self.combineK(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 304a79e752..4372ea0e26 100644 --- a/core/src/main/scala/cats/SemigroupK.scala +++ b/core/src/main/scala/cats/SemigroupK.scala @@ -29,11 +29,11 @@ import simulacrum.{op, typeclass} def combineK[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 combineK[A](x: F[G[A]], y: F[G[A]]): F[G[A]] = self.combineK(x, y) + def composeK[G[_]]: SemigroupK[λ[α => F[G[α]]]] = + new CompositeSemigroupK[F, G] { + implicit def F: SemigroupK[F] = self } /** @@ -44,3 +44,11 @@ import simulacrum.{op, typeclass} def combine(x: F[A], y: F[A]): F[A] = self.combineK(x, y) } } + +trait CompositeSemigroupK[F[_],G[_]] + extends SemigroupK[λ[α => F[G[α]]]] { + + implicit def F: SemigroupK[F] + + def combineK[A](x: F[G[A]], y: F[G[A]]): F[G[A]] = F.combineK(x, y) +} diff --git a/core/src/main/scala/cats/data/OneAnd.scala b/core/src/main/scala/cats/data/OneAnd.scala index da930e71ff..5d4f89ad1e 100644 --- a/core/src/main/scala/cats/data/OneAnd.scala +++ b/core/src/main/scala/cats/data/OneAnd.scala @@ -157,6 +157,7 @@ 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)) } + } trait OneAndLowPriority2 extends OneAndLowPriority1 { diff --git a/tests/src/test/scala/cats/tests/ComposeTests.scala b/tests/src/test/scala/cats/tests/ComposeTests.scala index 7c847b0626..4ba655529c 100644 --- a/tests/src/test/scala/cats/tests/ComposeTests.scala +++ b/tests/src/test/scala/cats/tests/ComposeTests.scala @@ -2,7 +2,7 @@ package cats package tests import cats.data.{ NonEmptyList, NonEmptyVector, OneAnd } -import cats.laws.discipline.{ ApplicativeTests, FoldableTests, CartesianTests, SemigroupKTests, arbitrary, eq }, arbitrary._, eq._ +import cats.laws.discipline.{ AlternativeTests, ApplicativeTests, FoldableTests, CartesianTests, MonoidKTests, SemigroupKTests, arbitrary, eq }, arbitrary._, eq._ import org.scalacheck.Arbitrary class ComposeTests extends CatsSuite { @@ -12,6 +12,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 = CartesianTests.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 +39,18 @@ class ComposeTests extends CatsSuite { } { - // Reducible composition + // MonoidK composition + + implicit val monoidKListVector: MonoidK[Lambda[A => List[Vector[A]]]] = MonoidK[List].composeK[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]) @@ -51,7 +59,7 @@ class ComposeTests extends CatsSuite { { // SemigroupK composition - implicit val semigroupKListVector: SemigroupK[Lambda[A => List[Vector[A]]]] = SemigroupK[List] compose SemigroupK[Vector] + implicit val semigroupKListVector: SemigroupK[Lambda[A => List[Vector[A]]]] = SemigroupK[List].composeK[Vector] checkAll("SemigroupK[Lambda[A => List[Vector[A]]]]", SemigroupKTests[Lambda[A => List[Vector[A]]]].semigroupK[Int]) }