Skip to content

Instantly share code, notes, and snippets.

@slouc
Last active May 2, 2017 19:40
Show Gist options
  • Save slouc/4deeddab2b93b6f08e0086cff845dcd5 to your computer and use it in GitHub Desktop.
Save slouc/4deeddab2b93b6f08e0086cff845dcd5 to your computer and use it in GitHub Desktop.
ShapelessGenericTypeClassDerivation
import shapeless._
object Basic {
case class Person(firstName: String, age: Int, married: Boolean)
case class Band(name: String, numOfAlbums: Int, stillActive: Boolean)
def personSerialized(p: Person): List[String] = List(p.firstName, p.age.toString, p.married.toString)
def bandSerialized(b: Band): List[String] = List(b.name, b.numOfAlbums.toString, b.stillActive.toString)
val genericPerson: shapeless.::[String, shapeless.::[Int, shapeless.::[Boolean, shapeless.HNil]]] =
Generic[Person].to(Person("David", 29, false))
val genericBand: shapeless.::[String, shapeless.::[Int, shapeless.::[Boolean, shapeless.HNil]]] =
Generic[Band].to(Band("Muse", 7, true))
def genericSerialized(gen: String :: Int :: Boolean :: HNil): List[String] =
List(gen(0), gen(1).toString, gen(2).toString)
val gp: List[String] = genericSerialized(genericPerson) // List(David, 29, false)
val gb: List[String] = genericSerialized(genericBand) // List(Muse, 7, true)
}
object TypeClasses {
import Basic._
trait SerializedEncoder[A] {
def encode(value: A): List[String]
}
object SerializedEncoder {
def apply[A](implicit enc: SerializedEncoder[A]): SerializedEncoder[A] = enc
}
// These two encoders below are specific for each product type.
implicit val personEncoder: SerializedEncoder[Person] =
new SerializedEncoder[Person] {
def encode(p: Person) = List(p.firstName, p.age.toString, if (p.married) "yes" else "no")
}
implicit val bandEncoder: SerializedEncoder[Band] =
new SerializedEncoder[Band] {
def encode(b: Band) = List(b.name, b.numOfAlbums.toString, if (b.stillActive) "yes" else "no")
}
// 1. If we have type class instances for the head and tail of an HList,
// we can derive an instance for the whole HList.
// 2. If we have a case class A, a Generic[A], and a type class instance for the generic’s Repr,
// we can combine them to create an instance for A.
def createEncoder[A](func: A => List[String]): SerializedEncoder[A] =
new SerializedEncoder[A] {
def encode(value: A): List[String] = func(value)
}
implicit val stringEncoder: SerializedEncoder[String] = createEncoder(str => List(str))
implicit val intEncoder: SerializedEncoder[Int] = createEncoder(num => List(num.toString))
implicit val booleanEncoder: SerializedEncoder[Boolean] = createEncoder(bool => List(if (bool) "yes" else "no"))
implicit val hnilEncoder: SerializedEncoder[HNil] = createEncoder(hnil => Nil)
implicit def hlistEncoder[H, T <: HList](implicit
hEncoder: SerializedEncoder[H],
tEncoder: SerializedEncoder[T]
): SerializedEncoder[H :: T] =
createEncoder {
case h :: t => hEncoder.encode(h) ++ tEncoder.encode(t)
}
// Taken together, these five instances allow us to summon SerializedEncoders
// for any HList involving Strings, Ints, and Booleans.
// First let's create one for Person, then we'll do the really generic one.
implicit val genericPersonEncoder: SerializedEncoder[Person] = {
val gen = Generic[Person]
val enc = SerializedEncoder[gen.Repr]
createEncoder(person => enc.encode(gen.to(person)))
}
// Let's now follow the same principle for generic encoder.
//
// First, we go from A to generic representation of A (e.g. a HList).
// We also know that we have an encoder for *any generic* repr (with the help of those five implicit encoders).
// We can use that implicitly available encoder to turn our generic repr of A into a List[String].
//
// What we just described is a function A => List[String]. Once we have that function, it's easy to construct
// a SerializedEncoder[A] since that's all it needs (we can use the utility function createEncoder for this):
implicit def genericEncoder[A, R](implicit
gen: Generic[A] {type Repr = R},
enc: SerializedEncoder[R]): SerializedEncoder[A] =
createEncoder(a => enc.encode(gen.to(a)))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment