Skip to content

Commit

Permalink
Add a Binested data type
Browse files Browse the repository at this point in the history
  • Loading branch information
iravid committed May 21, 2018
1 parent 41d3dbc commit e9f80d9
Show file tree
Hide file tree
Showing 8 changed files with 173 additions and 4 deletions.
93 changes: 93 additions & 0 deletions core/src/main/scala/cats/data/Binested.scala
Original file line number Diff line number Diff line change
@@ -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 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 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
}
}

trait BinestedInstances0 {
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
}

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)))
}
}

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(_))
}
}
4 changes: 3 additions & 1 deletion core/src/main/scala/cats/syntax/all.scala
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,6 @@ trait AllSyntaxBinCompat0
with ApplicativeErrorExtension
with TrySyntax

trait AllSyntaxBinCompat1 extends FlatMapOptionSyntax
trait AllSyntaxBinCompat1
extends FlatMapOptionSyntax
with BinestedSyntax
13 changes: 13 additions & 0 deletions core/src/main/scala/cats/syntax/binested.scala
Original file line number Diff line number Diff line change
@@ -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)
}
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 @@ -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
Expand Down
3 changes: 3 additions & 0 deletions laws/src/main/scala/cats/laws/discipline/Arbitrary.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down
2 changes: 1 addition & 1 deletion testkit/src/main/scala/cats/tests/CatsSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ package tests
import catalysts.Platform

import cats.instances.AllInstances
import cats.syntax.{AllSyntax, AllSyntaxBinCompat0, AllSyntaxBinCompat1, EqOps}
import cats.syntax.{ AllSyntax, AllSyntaxBinCompat0, AllSyntaxBinCompat1, EqOps }

import org.scalactic.anyvals.{PosZDouble, PosInt, PosZInt}
import org.scalatest.{FunSuite, FunSuiteLike, Matchers}
Expand Down
51 changes: 51 additions & 0 deletions tests/src/test/scala/cats/tests/BinestedSuite.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
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, ?, ?]]))
}

test("simple syntax-based usage") {
forAll { value: (Option[Int], List[Int]) =>
value.binested.bimap(_.toString, _.toString).value should ===(value.bimap(_.map(_.toString), _.map(_.toString)))
}
}
}
10 changes: 8 additions & 2 deletions tests/src/test/scala/cats/tests/SyntaxSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 }


/**
Expand All @@ -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 = ???
Expand Down Expand Up @@ -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
}
}

0 comments on commit e9f80d9

Please sign in to comment.