Skip to content

Commit

Permalink
Add composition to Representable (#3831)
Browse files Browse the repository at this point in the history
* Add composition to Representable

* Representable for Nested

Aux all the things

* This is why we have newtypes 🤦

* Make scala 2.12 implicit (explicit?) resolution happy

* Formatting

* Remove nested representable class as it doesn't pay nicely with Aux

* Remove unnecessary implicits
  • Loading branch information
TimWSpence authored Mar 29, 2021
1 parent e4b34e0 commit d56a856
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 1 deletion.
24 changes: 23 additions & 1 deletion core/src/main/scala/cats/Representable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ package cats
* Inspired by the Haskell representable package
* http://hackage.haskell.org/package/representable-functors-3.2.0.2/docs/Data-Functor-Representable.html
*/
trait Representable[F[_]] extends Serializable {
trait Representable[F[_]] extends Serializable { self =>

def F: Functor[F]

Expand Down Expand Up @@ -58,6 +58,28 @@ trait Representable[F[_]] extends Serializable {
* }}}
*/
def tabulate[A](f: Representation => A): F[A]

def compose[G[_]](implicit
G: Representable[G]
): Representable.Aux[λ[α => F[G[α]]], (self.Representation, G.Representation)] =
new Representable[λ[α => F[G[α]]]] { inner =>
override val F = self.F.compose(G.F)

type Representation = (self.Representation, G.Representation)

def index[A](f: F[G[A]]): Representation => A = (repr: Representation) => {
val ga: G[A] = self.index(f).apply(repr._1)
G.index(ga).apply(repr._2)
}

def tabulate[A](f: Representation => A): F[G[A]] = {
val fc: self.Representation => (G.Representation => A) = (rf: self.Representation) =>
(rg: G.Representation) => f((rf, rg))

self.F.map(self.tabulate(fc))(G.tabulate(_))
}

}
}

private trait RepresentableMonad[F[_], R] extends Monad[F] {
Expand Down
18 changes: 18 additions & 0 deletions core/src/main/scala/cats/data/Nested.scala
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,24 @@ sealed abstract private[data] class NestedInstances0 extends NestedInstances1 {
implicit val F: Functor[F] = F0
implicit val G: FunctorFilter[G] = G0
}

implicit def catsDataRepresentableForNested[F[_], G[_]](implicit
F0: Representable[F],
G0: Representable[G]
): Representable.Aux[Nested[F, G, *], (F0.Representation, G0.Representation)] = new Representable[Nested[F, G, *]] {
val FG = F0.compose(G0)

val F = new NestedFunctor[F, G] {
val FG = F0.F.compose(G0.F)
}

type Representation = FG.Representation

def index[A](f: Nested[F, G, A]): Representation => A = FG.index(f.value)

def tabulate[A](f: Representation => A): Nested[F, G, A] = Nested(FG.tabulate(f))
}

}

sealed abstract private[data] class NestedInstances1 extends NestedInstances2 {
Expand Down
31 changes: 31 additions & 0 deletions tests/src/test/scala/cats/tests/NestedSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import cats.laws.discipline.SemigroupalTests.Isomorphisms._
import cats.laws.discipline.arbitrary._
import cats.laws.discipline.eq._
import org.scalacheck.Test.Parameters
import org.scalacheck.Arbitrary

class NestedSuite extends CatsSuite {
// we have a lot of generated lists of lists in these tests. We have to tell
Expand Down Expand Up @@ -265,6 +266,36 @@ class NestedSuite extends CatsSuite {
)
}

{
type Pair[A] = (A, A)

//Scala 2.12 implicit resolution absolutely loses its mind here
implicit val help_scala2_12: Representable.Aux[Nested[Pair, Pair, *], (Boolean, Boolean)] =
Nested.catsDataRepresentableForNested[Pair, Pair]

val a: Arbitrary[Int] = implicitly[Arbitrary[Int]]
val b: Arbitrary[Nested[Pair, Pair, Int]] = implicitly[Arbitrary[Nested[Pair, Pair, Int]]]
val c: Arbitrary[(Boolean, Boolean)] = implicitly[Arbitrary[(Boolean, Boolean)]]
val d: Arbitrary[((Boolean, Boolean)) => Int] = implicitly[Arbitrary[((Boolean, Boolean)) => Int]]
val e: Eq[Nested[Pair, Pair, Int]] = Eq[Nested[Pair, Pair, Int]]
val f: Eq[Int] = Eq[Int]

checkAll(
"Nested[Pair, Pair, *]",
RepresentableTests[Nested[Pair, Pair, *], (Boolean, Boolean)].representable[Int](
a,
b,
c,
d,
e,
f
)
)
checkAll("Representable[Nested[Pair, Pair, *]]",
SerializableTests.serializable(Representable[Nested[Pair, Pair, *]])
)
}

{
// Align composition
checkAll(
Expand Down
10 changes: 10 additions & 0 deletions tests/src/test/scala/cats/tests/RepresentableSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,16 @@ class RepresentableSuite extends CatsSuite {
checkAll("Pair[String, String] <-> Boolean => String", RepresentableTests[Pair, Boolean].representable[String])
checkAll("Representable[Pair]", SerializableTests.serializable(Representable[Pair]))

{
implicit val rep: Representable.Aux[λ[α => Pair[Pair[α]]], (Boolean, Boolean)] =
Representable[Pair].compose[Pair]

checkAll("Pair[Pair[String, String]] <-> (Boolean, Boolean) => String",
RepresentableTests[λ[α => Pair[Pair[α]]], (Boolean, Boolean)].representable[String]
)
checkAll("Representable[Id[Pair]]", SerializableTests.serializable(Representable[λ[α => Id[Pair[α]]]]))
}

checkAll("Eval[Int] <-> Unit => Int", RepresentableTests[Eval, Unit].representable[Int])
checkAll("Representable[Eval]", SerializableTests.serializable(Representable[Eval]))

Expand Down

0 comments on commit d56a856

Please sign in to comment.