diff --git a/core/src/main/scala/cats/data/Binested.scala b/core/src/main/scala/cats/data/Binested.scala new file mode 100644 index 00000000000..5ce3a8fc228 --- /dev/null +++ b/core/src/main/scala/cats/data/Binested.scala @@ -0,0 +1,93 @@ +package cats +package data + +import cats.arrow._ + +/** Compose a two-slot type constructor `F[_, _]` with two single-slot type constructors + * `G[_]` and `H[_]`, resulting in a two-slot type constructor with respect to the inner types. + * For example, `List` and `Option` both have `Functor` instances, and `Either` has a + * `Bifunctor` instance. Therefore, `Binested[Either, List, Option, ?, ?]` has a `Bifunctor` + * instance as well: + * + * {{{ + * scala> import cats.Bifunctor + * scala> import cats.data.Binested + * scala> import cats.implicits._ + * scala> val eitherListOption: Either[List[Int], Option[String]] = Right(Some("cats")) + * scala> val f: Int => String = _.toString + * scala> val g: String => String = _ + "-bifunctor" + * scala> val binested = Binested(eitherListOption) + * scala> val bimapped = Bifunctor[Binested[Either, List, Option, ?, ?]].bimap(binested)(f, g).value + * res0: Either[List[String], Option[String]] = Right(Some("cats-bifunctor")) + * }}} + */ +final case class Binested[F[_, _], G[_], H[_], A, B](value: F[G[A], H[B]]) + +object Binested extends BinestedInstances + +trait BinestedInstances extends BinestedInstances0 { + implicit def catsDataEqForBinested[F[_, _], G[_], H[_], A, B]( + implicit F: Eq[F[G[A], H[B]]]): Eq[Binested[F, G, H, A, B]] = + Eq.by(_.value) + + implicit def catsDataBifunctorForBinested[F[_, _], G[_], H[_]]( + implicit F: Bifunctor[F], G: Functor[G], H: Functor[H]): Bifunctor[Binested[F, G, H, ?, ?]] = + new Bifunctor[Binested[F, G, H, ?, ?]] { + def bimap[A, B, C, D](fab: Binested[F, G, H, A, B])(f: A => C, g: B => D): Binested[F, G, H, C, D] = + Binested(F.bimap(fab.value)(G.map(_)(f), H.map(_)(g))) + } + + implicit def catsDataProfunctorForBinested[F[_, _], G[_], H[_]]( + implicit F: Profunctor[F], G: Functor[G], H: Functor[H]): Profunctor[Binested[F, G, H, ?, ?]] = + new Profunctor[Binested[F, G, H, ?, ?]] { + def dimap[A, B, C, D](fab: Binested[F, G, H, A, B])(f: C => A)(g: B => D): Binested[F, G, H, C, D] = + Binested(F.dimap(fab.value)(G.map(_: G[C])(f))(H.map(_)(g))) + } + + implicit def catsDataBifoldableForBinested[F[_, _], G[_], H[_]]( + implicit F0: Bifoldable[F], G0: Foldable[G], H0: Foldable[H]): Bifoldable[Binested[F, G, H, ?, ?]] = + new BinestedBifoldable[F, G, H] { + override implicit def F: Bifoldable[F] = F0 + override implicit def G: Foldable[G] = G0 + override implicit def H: Foldable[H] = H0 + } +} + +trait BinestedInstances0 { + implicit def catsDataBitraverseForBinested[F[_, _], G[_], H[_]]( + implicit F0: Bitraverse[F], H0: Traverse[H], G0: Traverse[G]): Bitraverse[Binested[F, G, H, ?, ?]] = + new BinestedBitraverse[F, G, H] { + override implicit def F: Bitraverse[F] = F0 + override implicit def G: Traverse[G] = G0 + override implicit def H: Traverse[H] = H0 + } +} + +sealed abstract class BinestedBifoldable[F[_, _], G[_], H[_]] extends Bifoldable[Binested[F, G, H, ?, ?]] { + implicit def F: Bifoldable[F] + implicit def G: Foldable[G] + implicit def H: Foldable[H] + + def bifoldLeft[A, B, C](fab: Binested[F, G, H, A, B], c: C)(f: (C, A) => C, g: (C, B) => C): C = + F.bifoldLeft(fab.value, c)( + (c, ga) => G.foldLeft(ga, c)(f), + (c, hb) => H.foldLeft(hb, c)(g) + ) + + + def bifoldRight[A, B, C](fab: Binested[F, G, H, A, B], c: Eval[C])(f: (A, Eval[C]) => Eval[C], g: (B, Eval[C]) => Eval[C]): Eval[C] = + F.bifoldRight(fab.value, c)( + (ga, ec) => G.foldRight(ga, ec)(f), + (hb, ec) => H.foldRight(hb, ec)(g) + ) +} + +sealed abstract class BinestedBitraverse[F[_, _], G[_], H[_]] extends BinestedBifoldable[F, G, H] with Bitraverse[Binested[F, G, H, ?, ?]] { + override implicit def F: Bitraverse[F] + override implicit def G: Traverse[G] + override implicit def H: Traverse[H] + + def bitraverse[I[_], A, B, C, D](fab: Binested[F, G, H, A, B])(f: A => I[C], g: B => I[D])(implicit I: Applicative[I]): I[Binested[F, G, H, C, D]] = { + I.map(F.bitraverse(fab.value)(G.traverse(_)(f), H.traverse(_)(g)))(Binested(_)) + } +} diff --git a/core/src/main/scala/cats/syntax/all.scala b/core/src/main/scala/cats/syntax/all.scala index dec09ed8e05..8e58a8e74f3 100644 --- a/core/src/main/scala/cats/syntax/all.scala +++ b/core/src/main/scala/cats/syntax/all.scala @@ -58,5 +58,6 @@ trait AllSyntaxBinCompat0 extends UnorderedTraverseSyntax with ApplicativeErrorExtension with TrySyntax + with BinestedSyntax trait AllSyntaxBinCompat1 extends FlatMapOptionSyntax diff --git a/core/src/main/scala/cats/syntax/binested.scala b/core/src/main/scala/cats/syntax/binested.scala new file mode 100644 index 00000000000..32a8016fbc8 --- /dev/null +++ b/core/src/main/scala/cats/syntax/binested.scala @@ -0,0 +1,13 @@ +package cats +package syntax + +import cats.data.Binested + +trait BinestedSyntax { + implicit final def catsSyntaxBinestedId[F[_, _], G[_], H[_], A, B](value: F[G[A], H[B]]): BinestedIdOps[F, G, H, A, B] = + new BinestedIdOps(value) +} + +final class BinestedIdOps[F[_, _], G[_], H[_], A, B](val value: F[G[A], H[B]]) extends AnyVal { + def binested: Binested[F, G, H, A, B] = Binested(value) +} diff --git a/core/src/main/scala/cats/syntax/package.scala b/core/src/main/scala/cats/syntax/package.scala index b928818bc9c..b49683707ce 100644 --- a/core/src/main/scala/cats/syntax/package.scala +++ b/core/src/main/scala/cats/syntax/package.scala @@ -10,6 +10,7 @@ package object syntax { object arrowChoice extends ArrowChoiceSyntax object bifunctor extends BifunctorSyntax object bifoldable extends BifoldableSyntax + object binested extends BinestedSyntax object bitraverse extends BitraverseSyntax @deprecated("use cats.syntax.semigroupal instead", "1.0.0-RC1") object cartesian extends SemigroupalSyntax diff --git a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index fce94163b4d..63f7df25718 100644 --- a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -228,6 +228,9 @@ object arbitrary extends ArbitraryInstances0 { implicit def catsLawsArbitraryForNested[F[_], G[_], A](implicit FG: Arbitrary[F[G[A]]]): Arbitrary[Nested[F, G, A]] = Arbitrary(FG.arbitrary.map(Nested(_))) + implicit def catsLawsArbitraryForBinested[F[_, _], G[_], H[_], A, B](implicit F: Arbitrary[F[G[A], H[B]]]): Arbitrary[Binested[F, G, H, A, B]] = + Arbitrary(F.arbitrary.map(Binested(_))) + implicit def catsLawArbitraryForState[S: Arbitrary: Cogen, A: Arbitrary]: Arbitrary[State[S, A]] = catsLawArbitraryForIndexedStateT[Eval, S, S, A] diff --git a/tests/src/test/scala/cats/tests/BinestedSuite.scala b/tests/src/test/scala/cats/tests/BinestedSuite.scala new file mode 100644 index 00000000000..29c4fae1e4d --- /dev/null +++ b/tests/src/test/scala/cats/tests/BinestedSuite.scala @@ -0,0 +1,45 @@ +package cats +package tests + +import cats.arrow.Profunctor +import cats.data.Binested + +import cats.laws.discipline._ +import cats.laws.discipline.arbitrary._ +import cats.laws.discipline.eq._ + +class BinestedSuite extends CatsSuite { + // we have a lot of generated lists of lists in these tests. We have to tell + // Scalacheck to calm down a bit so we don't hit memory and test duration + // issues. + implicit override val generatorDrivenConfig: PropertyCheckConfiguration = + PropertyCheckConfiguration(minSuccessful = 20, sizeRange = 5) + + { + // Bifunctor + Functor + Functor = Bifunctor + implicit val instance = ListWrapper.functor + checkAll("Binested[Either, ListWrapper, Option, ?, ?]", BifunctorTests[Binested[Either, ListWrapper, Option, ?, ?]].bifunctor[Int, Int, Int, String, String, String]) + checkAll("Bifunctor[Binested[Either, ListWrapper, Option, ?, ?]]", SerializableTests.serializable(Bifunctor[Binested[Either, ListWrapper, Option, ?, ?]])) + } + + { + // Profunctor + Functor + Functor = Profunctor + implicit val instance = ListWrapper.functor + checkAll("Binested[Function1, ListWrapper, Option, ?, ?]", ProfunctorTests[Binested[Function1, ListWrapper, Option, ?, ?]].profunctor[Int, Int, Int, String, String, String]) + checkAll("Profunctor[Binested[Function1, ListWrapper, Option, ?, ?]]", SerializableTests.serializable(Profunctor[Binested[Function1, ListWrapper, Option, ?, ?]])) + } + + { + // Bifoldable + foldable + foldable = Bifoldable + implicit val instance = ListWrapper.foldable + checkAll("Binested[Either, ListWrapper, ListWrapper, ?, ?]", BifoldableTests[Binested[Either, ListWrapper, ListWrapper, ?, ?]].bifoldable[Int, Int, Int]) + checkAll("Bifoldable[Binested[Either, ListWrapper, ListWrapper, ?, ?]]", SerializableTests.serializable(Bifoldable[Binested[Either, ListWrapper, ListWrapper, ?, ?]])) + } + + { + // Bitraverse + traverse + traverse = Bitraverse + implicit val instance = ListWrapper.traverse + checkAll("Binested[Either, ListWrapper, ListWrapper, ?, ?]", BitraverseTests[Binested[Either, ListWrapper, ListWrapper, ?, ?]].bitraverse[Option, Int, Int, Int, String, String, String]) + checkAll("Bitraverse[Binested[Either, ListWrapper, ListWrapper, ?, ?]]", SerializableTests.serializable(Bitraverse[Binested[Either, ListWrapper, ListWrapper, ?, ?]])) + } +} diff --git a/tests/src/test/scala/cats/tests/SyntaxSuite.scala b/tests/src/test/scala/cats/tests/SyntaxSuite.scala index 6bcd3b987b4..2b6a46f25cf 100644 --- a/tests/src/test/scala/cats/tests/SyntaxSuite.scala +++ b/tests/src/test/scala/cats/tests/SyntaxSuite.scala @@ -2,8 +2,9 @@ package cats package tests import cats.arrow.Compose +import cats.data.Binested import cats.instances.AllInstances -import cats.syntax.AllSyntax +import cats.syntax.{ AllSyntax, AllSyntaxBinCompat } /** @@ -24,7 +25,7 @@ import cats.syntax.AllSyntax * * None of these tests should ever run, or do any runtime checks. */ -object SyntaxSuite extends AllInstances with AllSyntax { +object SyntaxSuite extends AllSyntaxBinCompat with AllInstances with AllSyntax { // pretend we have a value of type A def mock[A]: A = ??? @@ -338,5 +339,10 @@ object SyntaxSuite extends AllInstances with AllSyntax { val gea4 = ga.recoverWith(pfegea) } + def testBinested[F[_, _], G[_], H[_], A, B]: Unit = { + val fgahb = mock[F[G[A], H[B]]] + + val binested: Binested[F, G, H, A, B] = fgahb.binested + } }