Last active
July 27, 2017 18:39
-
-
Save jmccance/d6009d23a11a593ddb5e9aba49b1a11a to your computer and use it in GitHub Desktop.
Deriving Show with Shapeless
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name := "shapeless-lightning-talk" | |
scalaVersion := "2.12.2" | |
libraryDependencies ++= Seq( | |
"com.chuusai" %% "shapeless" % "2.3.2" | |
) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package examples | |
import shapeless._ | |
import shapeless.labelled.FieldType | |
// First we need a typeclass to demo shapeless with. This one just handles converting things | |
// to strings. | |
trait Show[A] { | |
def apply(a: A): String | |
} | |
object Show { | |
// Add a `show` extension method to classes so that we can more easily run the pretty-printer on | |
// it. | |
implicit class ShowOps[A](val a: A) extends AnyVal { | |
def show(implicit showA: Show[A]): String = showA(a) | |
} | |
// Derive some instances for Double, String, and List. | |
implicit val showDouble: Show[Double] = x => x.toString | |
implicit val showString: Show[String] = s => s.toString | |
implicit def showList[A](implicit showA: Show[A]): Show[List[A]] = | |
as => as.map(showA(_)).mkString("[\n ", ",\n ", "\n]") | |
// Next we'll implement some instances to handle product types. | |
// The base case is just the empty string; we don't care about rendering empty products. | |
implicit val showHNil: Show[HNil] = _ => "" | |
// Now we'll render an HList as comma-separated values. `K` is the "key" of the labels, which are | |
// just symbols (`'foo`, `'bar`, etc.). The HList is an HList of FieldTypes, giving them their | |
// labels. | |
implicit def showHList[K <: Symbol, H, T <: HList](implicit | |
witness: Witness.Aux[K], | |
showH: Show[H], | |
showT: Show[T]): Show[FieldType[K, H] :: T] = { | |
// Unpack the field name out of the witness. | |
val fieldName = witness.value.name | |
{ | |
case h :: HNil => s"$fieldName = ${showH(h)}" | |
case h :: t => s"$fieldName = ${showH(h)}, ${showT(t)}" | |
} | |
} | |
// Next, ADTs. Again, the base case is just the empty string. | |
implicit val showCNil: Show[CNil] = _ => "" | |
// The coproduct case is also labeled, with the same basic structure as the HList. | |
implicit def showCoproduct[K <: Symbol, L, R <: Coproduct](implicit | |
witness: Witness.Aux[K], | |
showL: Lazy[Show[L]], | |
showR: Show[R]): Show[FieldType[K, L] :+: R] = { | |
val typeName = witness.value.name | |
{ | |
case Inl(left) => s"$typeName ${showL.value(left)}" | |
case Inr(right) => showR(right) | |
} | |
} | |
// Separate generic derivers for case classes and ADTs allows us to wrap the case classes in | |
// parens. Not sure how to render them as (e.g.) `Point(x = 1.0, y = 1.0)`, but the parens at | |
// least make it clearer. | |
implicit def showCaseClass[A, R <: HList](implicit | |
genericA: LabelledGeneric[A] {type Repr = R}, | |
showR: Lazy[Show[R]]): Show[A] = { a => | |
s"(${showR.value(genericA.to(a))})" | |
} | |
implicit def showAdt[A, R <: Coproduct](implicit | |
genericA: LabelledGeneric[A] {type Repr = R}, | |
showR: Lazy[Show[R]]): Show[A] = { a => | |
showR.value(genericA.to(a)) | |
} | |
} | |
object Shapes extends App { | |
import Show.ShowOps | |
case class Point(x: Double, y: Double) | |
sealed trait Shape extends Product with Serializable | |
case class Circle(radius: Double, origin: Point) extends Shape | |
case class Rectangle(lowerLeft: Point, upperRight: Point) extends Shape | |
println( | |
List( | |
Circle(1.0, Point(0.0, 0.0)), | |
Rectangle(Point(-0.5, -0.5), Point(0.5, 0.5)) | |
).show | |
) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment