Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework Show derivation #154

Merged
merged 1 commit into from
Jun 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions core/src/main/scala/cats/derived/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ object auto {

object show {
implicit def kittensMkShow[A](
implicit refute: Refute[Show[A]], show: MkShow[A]
): Show[A] = show
implicit refute: Refute[Show[A]], ev: Lazy[MkShow[A]]
): Show[A] = ev.value
}

object showPretty {
Expand Down Expand Up @@ -210,8 +210,8 @@ object cached {

object show {
implicit def kittensMkshow[A](
implicit refute: Refute[Show[A]], ev: Cached[MkShow[A]])
: Show[A] = ev.value
implicit refute: Refute[Show[A]], cached: Cached[MkShow[A]]
): Show[A] = cached.value
}

object showPretty {
Expand Down Expand Up @@ -302,7 +302,7 @@ object semi {

def applicative[F[_]](implicit F: Lazy[MkApplicative[F]]): Applicative[F] = F.value

def show[A](implicit ev: MkShow[A]): Show[A] = ev
def show[A](implicit ev: Lazy[MkShow[A]]): Show[A] = ev.value

def showPretty[A](implicit ev: MkShowPretty[A]): ShowPretty[A] = ev

Expand Down
94 changes: 38 additions & 56 deletions core/src/main/scala/cats/derived/show.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package cats.derived
package cats
package derived

import cats.Show
import shapeless._, labelled._
import shapeless._
import shapeless.labelled._

import scala.annotation.implicitNotFound

/**
* Due to a limitation in the way Shapeless' `describe` is currently
Expand All @@ -15,69 +18,48 @@ import shapeless._, labelled._
* See the test suite for more precise examples of what can and cannot
* be derived.
*/
@implicitNotFound("Could not derive an instance of Show[${A}]")
trait MkShow[A] extends Show[A]

object MkShow extends MkShowDerivation {
def apply[A](implicit show: MkShow[A]): MkShow[A] = show
def apply[A](implicit ev: MkShow[A]): MkShow[A] = ev
}

trait MkShowDerivation extends MkShow1 {
implicit val emptyProductDerivedShow: MkShow[HNil] =
instance(_ => "")

implicit def productDerivedShow[K <: Symbol, V, T <: HList](
implicit key: Witness.Aux[K],
showV: Show[V] OrElse MkShow[V],
showT: MkShow[T]): MkShow[FieldType[K, V] :: T] = instance { fields =>
val fieldName = key.value.name
val fieldValue = showV.unify.show(fields.head)
val nextFields = showT.show(fields.tail)

if (nextFields.isEmpty)
s"$fieldName = $fieldValue"
else
s"$fieldName = $fieldValue, $nextFields"
private[derived] abstract class MkShowDerivation {
implicit val mkShowHNil: MkShow[HNil] = instance(_ => "")
implicit val mkShowCNil: MkShow[CNil] = instance(_ => "")

implicit def mkShowLabelledHCons[K <: Symbol, V, T <: HList](
implicit K: Witness.Aux[K], V: Show[V] OrElse MkShow[V], T: MkShow[T]
): MkShow[FieldType[K, V] :: T] = instance { case v :: t =>
val name = K.value.name
val value = V.unify.show(v)
val tail = T.show(t)
if (tail.isEmpty) s"$name = $value"
else s"$name = $value, $tail"
}


implicit def emptyCoproductDerivedShow: MkShow[CNil] =
instance(_ => "")

}

trait MkShow1 extends MkShow2 {
// used when Show[V] (a member of the coproduct) has to be derived.
implicit def coproductDerivedShow[K <: Symbol, V, T <: Coproduct](
implicit key: Witness.Aux[K],
showV: Show[V] OrElse MkShow[V],
showT: MkShow[T]): MkShow[FieldType[K, V] :+: T] = instance {
case Inl(l) => showV.unify.show(l)
case Inr(r) => showT.show(r)
implicit def mkShowCCons[L, R <: Coproduct](
implicit L: Show[L] OrElse MkShow[L], R: MkShow[R]
): MkShow[L :+: R] = instance {
case Inl(l) => L.unify.show(l)
case Inr(r) => R.show(r)
}

}

trait MkShow2 extends MkShow3 {


implicit def genericDerivedShowProduct[A, R <: HList](
implicit repr: LabelledGeneric.Aux[A, R],
t: Typeable[A],
s: Lazy[MkShow[R]]): MkShow[A] = instance { a =>
val name = t.describe.takeWhile(_ != '[')
val contents = s.value.show(repr.to(a))

s"$name($contents)"
implicit def mkShowGenericProduct[A, R <: HList](
implicit A: LabelledGeneric.Aux[A, R], T: Typeable[A], R: Lazy[MkShow[R]]
): MkShow[A] = instance { a =>
val name = T.describe.takeWhile(_ != '[')
val fields = R.value.show(A.to(a))
s"$name($fields)"
}
}

trait MkShow3 {
protected def instance[A](body: A => String): MkShow[A] = new MkShow[A] {
def show(value: A): String = body(value)
}
implicit def mkShowGenericCoproduct[A, R <: Coproduct](
implicit A: Generic.Aux[A, R], R: Lazy[MkShow[R]]
): MkShow[A] = instance(a => R.value.show(A.to(a)))

implicit def genericDerivedShowCoproduct[A, R <: Coproduct](
implicit repr: LabelledGeneric.Aux[A, R],
s: Lazy[MkShow[R]]): MkShow[A] =
instance(a => s.value.show(repr.to(a)))
private def instance[A](f: A => String): MkShow[A] =
new MkShow[A] {
def show(value: A): String = f(value)
}
}
189 changes: 78 additions & 111 deletions core/src/test/scala/cats/derived/show.scala
Original file line number Diff line number Diff line change
@@ -1,142 +1,109 @@
package cats
package derived

import cats.Show
import cats.instances.all._
import shapeless.test.illTyped
import TestDefns._

class ShowTests extends KittensSuite {
class ShowSuite extends KittensSuite {
import ShowSuite._
import TestDefns._


test("Simple case classes") {
implicit val sf = semi.show[Foo]
val foo = Foo(42, Option("Hello"))
val printedFoo = "Foo(i = 42, b = Some(Hello))"

assert(foo.show == printedFoo)
}

test("Nested case classes auto derive inner class") {
implicit val so = semi.show[Outer]

val nested = Outer(Inner(3))
val printedNested = "Outer(in = Inner(i = 3))"

assert(nested.show == printedNested)
implicit val showAddress: Show[Address] = Show.show { a =>
List(a.street, a.city, a.state).mkString(" ")
}

test("respect defined instance") {
import InnerInstance._
implicit val so = semi.show[Outer]

val printedNested = "Outer(in = Blah)"
val nested = Outer(Inner(3))

assert(nested.show == printedNested)
}

test("respect defined instance with full auto derivation") {
import InnerInstance._
import auto.show._

val printedNested = "Outer(in = Blah)"
val nested = Outer(Inner(3))

assert(nested.show == printedNested)
}

test("Recursive ADTs with no type parameters") {
implicit val st = semi.show[IntTree]

val tree: IntTree = IntNode(IntLeaf(1), IntNode(IntNode(IntLeaf(2), IntLeaf(3)), IntLeaf(4)))
val printedTree =
"IntNode(l = IntLeaf(t = 1), r = IntNode(l = IntNode(l = IntLeaf(t = 2), r = IntLeaf(t = 3)), r = IntLeaf(t = 4)))"

assert(tree.show == printedTree)
}

test("Non recursive ADTs with type parameters") {
implicit val sg = {
import auto.show._
semi.show[GenericAdt[Int]]
def testShow(context: String)(
implicit foo: Show[Foo],
outer: Show[Outer],
intTree: Show[IntTree],
genericAdt: Show[GenericAdt[Int]],
people: Show[People],
listField: Show[ListField],
interleaved: Show[Interleaved[Int]],
boxBogus: Show[Box[Bogus]]
): Unit = {
test(s"$context.Show[Foo]") {
val value = Foo(42, Option("Hello"))
val shown = "Foo(i = 42, b = Some(Hello))"
assert(value.show == shown)
}

val genAdt: GenericAdt[Int] = GenericAdtCase(Some(1))
val printedGenAdt = "GenericAdtCase(value = Some(1))"

assert(genAdt.show == printedGenAdt)
}

test("Recursive ADTs with type parameters are not supported") {
import auto.show._

val tree: Tree[Int] = Node(Leaf(1), Node(Node(Leaf(2), Leaf(3)), Leaf(4)))
val printedTree =
"Node(l = Leaf(t = 1), r = Node(l = Node(l = Leaf(t = 2), r = Leaf(t = 3)), r = Leaf(t = 4)))"

illTyped("Show[Tree[Int]]")
}

test("Deep type hierarchy") {
semi.show[Top]
semi.show[People]
}
test(s"$context.Show[Outer]") {
val value = Outer(Inner(3))
val shown = "Outer(in = Inner(i = 3))"
assert(value.show == shown)
}

test("Deep type hierarchy respect existing instance") {
implicit val sAdd : Show[Address] = new Show[Address] {
def show(t: Address) = t.street + " " + t.city + " " + t.state
test(s"$context.Show[IntTree]") {
val value: IntTree = IntNode(IntLeaf(1), IntNode(IntNode(IntLeaf(2), IntLeaf(3)), IntLeaf(4)))
val shown = "IntNode(l = IntLeaf(t = 1), r = IntNode(l = IntNode(l = IntLeaf(t = 2), r = IntLeaf(t = 3)), r = IntLeaf(t = 4)))"
assert(value.show == shown)
}
assert(semi.show[People].show(People(name = "Kai",
contactInfo = ContactInfo(
phoneNumber = "303-123-4567",
address = Address(
street = "123 1st St",
city = "New York", state = "NY") ))) == "People(name = Kai, contactInfo = ContactInfo(phoneNumber = 303-123-4567, address = 123 1st St New York NY))")
}

test("Deep type hierarchy respect existing instance in full auto derivation") {
implicit val sAdd : Show[Address] = new Show[Address] {
def show(t: Address) = t.street + " " + t.city + " " + t.state
test(s"$context.Show[GenericAdt[Int]]") {
val value: GenericAdt[Int] = GenericAdtCase(Some(1))
val shown = "GenericAdtCase(value = Some(1))"
assert(value.show == shown)
}
import auto.show._
assert(People(name = "Kai",
contactInfo = ContactInfo(
phoneNumber = "303-123-4567",
address = Address(
street = "123 1st St",
city = "New York", state = "NY") )).show == "People(name = Kai, contactInfo = ContactInfo(phoneNumber = 303-123-4567, address = 123 1st St New York NY))")
}

test(s"$context.Show[People]") {
val value = People("Kai", ContactInfo("303-123-4567", Address("123 1st St", "New York", "NY")))
val shown = "People(name = Kai, contactInfo = ContactInfo(phoneNumber = 303-123-4567, address = 123 1st St New York NY))"
assert(value.show == shown)
}

test(s"$context.Show[ListField]") {
val value = ListField("a", List(ListFieldChild(1)))
val shown = "ListField(a = a, b = List(ListFieldChild(c = 1)))"
assert(value.show == shown)
}

test("semi-auto derivation respect existing instance") {
implicit val lifShow: Show[ListField] = {
import auto.show._
semi.show
test(s"$context.Show[Interleaved[Int]]") {
val value = Interleaved(1, 2, 3, List(4, 5, 6), "789")
val shown = "Interleaved(i = 1, t = 2, l = 3, tt = List(4, 5, 6), s = 789)"
assert(value.show == shown)
}

assert(ListField(a ="a", b = List(ListFieldChild(c = 1))).show ==
"ListField(a = a, b = List(ListFieldChild(c = 1)))"
)
test(s"$context.Show respects existing instances") {
val value = Box(Bogus(42))
val shown = "Box(content = Blah)"
assert(value.show == shown)
}
}
test("auto derivation respect existing instance") {
import auto.show._

assert(ListField(a ="a", b = List(ListFieldChild(c = 1))).show ==
"ListField(a = a, b = List(ListFieldChild(c = 1)))"
)
{
import auto.show._
testShow("auto")
illTyped("Show[Tree[Int]]")
}

test("derives an instance for Interleaved[T]") {
semi.show[TestDefns.Interleaved[Int]]
{
import cached.show._
testShow("cached")
illTyped("Show[Tree[Int]]")
}

semiTests.run()

object semiTests {
implicit val foo: Show[Foo] = semi.show
implicit val outer: Show[Outer] = semi.show
implicit val intTree: Show[IntTree] = semi.show
implicit val genericAdt: Show[GenericAdt[Int]] = semi.show
implicit val people: Show[People] = semi.show
implicit val listFieldChild: Show[ListFieldChild] = semi.show
implicit val listField: Show[ListField] = semi.show
implicit val interleaved: Show[Interleaved[Int]] = semi.show
implicit val boxBogus: Show[Box[Bogus]] = semi.show
illTyped("semi.show[Tree[Int]]")
def run(): Unit = testShow("semi")
}
}

object ShowSuite {

object InnerInstance {
implicit def showInner: Show[Inner] = new Show[Inner]{
def show(t: Inner): String = "Blah"
final case class Bogus(value: Int)
object Bogus {
implicit val show: Show[Bogus] = Show.show(_ => "Blah")
}
}
14 changes: 11 additions & 3 deletions core/src/test/scala/cats/derived/showPretty.scala
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package cats
package derived

import cats.Show
import cats.instances.all._
import shapeless.test.illTyped
import TestDefns._

class ShowPrettyTests extends KittensSuite {
import TestDefns._

test("Simple case classes") {
implicit val sf = semi.showPretty[Foo]
implicit val sf: ShowPretty[Foo] = semi.showPretty[Foo]
val foo = Foo(42, Option("Hello"))
val printedFoo =
"""
Expand Down Expand Up @@ -212,3 +212,11 @@ class ShowPrettyTests extends KittensSuite {
}

}

object InnerInstance {
import TestDefns.Inner

implicit def showInner: Show[Inner] = new Show[Inner]{
def show(t: Inner): String = "Blah"
}
}