SlideShare a Scribd company logo
1 of 32
Download to read offline
AUTOMATIC TYPE CLASS DERIVATION
WITH SHAPELESS
Shi Forward Tech Talks
July 6, 2017 / Joao Azevedo
The purpose of this talk is to give the basics on how to do automatic type class derivation using Shapeless. At the end of the
talk, attendees should be aware of the building blocks Shapeless provides and be able to apply some of the described patterns
to derive their own type classes.
INTRODUCTION
TYPE CLASSES
A definition of behaviour in the form of operations that
must be supported by a given type.
A way to implement ad hoc polymorphism.
TYPE CLASSES
def mean[T](xs: Vector[T])(implicit ev: NumberLike[T]): T =
ev.divide(xs.reduce(ev.plus(_, _)), xs.size)
SHAPELESS
You must be shapeless, formless, like water.
When you pour water in a cup, it becomes
the cup. When you pour water in a bottle, it
becomes the bottle. When you pour water
in a teapot, it becomes the teapot. Water
can drip and it can crash. Become like
water my friend. -- Bruce Lee
SHAPELESS
A type class and dependent type based generic
programming library for Scala.
Generic programming provides ways to exploit similarities
between types to avoid repetition.
case class Employee(name: String, number: Int, manager: Boolean)
case class IceCream(name: String, numCherries: Int, inCone: Boolean)
Sometimes, types are too specific and we want to explore similarities in their shape to avoid repetition. In the presented
example, even though Employee and IceCream are different types, they share the same shape: they both contain three fields of
the same types: String, Int and Boolean.
A RANDOM EXAMPLE
We start with a practical example to show some of the shapeless concepts that are relevant for automatic derivation of type
classes.
A RANDOM EXAMPLE
trait Random[A] extends Function0[A]
object Random {
def apply[A](implicit r: Random[A]) = r()
}
Random[Int]
// 578073111
Random[Double]
// 0.48802405390668835
Random[String]
// mbbHlZAam
Random[Employee]
// Employee(Y7F,1976420522,true)
Random[IceCream]
// IceCream(CywOmpJd,-335712437,false)
The idea is to have a Random typeclass that is capable of producing "random" instances of a given type.
A RANDOM EXAMPLE
implicit val rInt: Random[Int] =
() => util.Random.nextInt
implicit val rDouble: Random[Double] =
() => util.Random.nextDouble
implicit val rString: Random[String] =
() => util.Random.alphanumeric.take(
util.Random.nextInt(10)).mkString
implicit val rBoolean: Random[Boolean] =
() => util.Random.nextBoolean
We can start by implementing Random instances for some base types. The String implementation is obviously incorrect, but the
specific implementation is not very relevant for now. We just want to make sure we have a typeclass for some base types.
A RANDOM EXAMPLE
implicit def rEmployee(implicit rs: Random[String],
ri: Random[Int],
rb: Random[Boolean]): Random[Employee] =
() => Employee(rs(), ri(), rb())
implicit def rIceCream(implicit rs: Random[String],
ri: Random[Int],
rb: Random[Boolean]): Random[IceCream] =
() => IceCream(rs(), ri(), rb())
Having implementations of Random for some base types, we can have implementations of Random for more complex types that
base themselves on the Random implementations for the types of fields that compose those types.
However, we still have two distinct implementations for Employee and IceCream even though they're remarkably similar. Can
we do better?
SHAPELESS TO THE RESCUE!
shapeless allows us to do better by taking advantage of the shape of the types and exploiting similarities between shapes.
HLISTS
"hello" :: 13 :: true :: HNil
: String :: Int :: Boolean :: HNil
The first important concept from shapeless that is very relevant for the purpose of automatic derivation of type classes are
HLists. HLists are a generic representation of products, much like Scala's built-in tuples. They're a bit more powerful than
Scala's tuples because each size of tuple has an unrelated type and we can't represent 0-length tuples. HLists are similar to
Scala's Lists, but, at compile time, we know both their size and the type of each element that composes the list.
RANDOM HLISTS
implicit val rHNil: Random[HNil] = () => HNil
implicit def rHList[H, T <: HList](
implicit rh: Random[H], rt: Random[T]): Random[H :: T] =
() => rh() :: rt()
Knowing what HLists are, we can create an implementation of Random for HLists, provided that we have implementations of
Random for the type of each element that the list is composed of. This already provides us with a generic implementation of
Random for products.
GENERIC
Generic[Employee]
// shapeless.Generic[Employee]{
// type Repr = String :: Int :: Boolean :: shapeless.HNil} =
// anon$macro$16$1@3f09ba38
Generic[IceCream]
// shapeless.Generic[IceCream]{
// type Repr = String :: Int :: Boolean :: shapeless.HNil} =
// anon$macro$12$1@750eba8f
trait Generic[T] {
type Repr
def to(t: T): Repr
def from(r: Repr): T
}
shapeless provides a type class called Generic that allows us to go from an algebraic data type to its generic representation as
an HList (and vice-versa). The Generic instance has a type member Repr containing the type of its generic representation.
GENERIC
val hlist = "a" :: 1 :: true :: HNil
Generic[Employee].from(hlist)
// Employee(a,1,true)
Generic[IceCream].from(hlist)
// IceCream(a,1,true)
Using Generic, we can take advantage of the shape of an ADT and convert back and forth from different ADTs if they have the
same Repr.
RANDOM PRODUCTS
implicit def rProduct[T](
implicit g: Generic[T], rg: Random[g.Repr]): Random[T] =
() => g.from(rg())
Unfortunately this doesn't compile.
Taking this into account, we can derive an implementation of Random for products if we are able to derive an implementation of
Random for their generic representation (which we already did for HLists in a previous slide).
Unfortunately this doesn't compile because we can't reference a type member of a parameter within the same parameter list the
parameter is in, and all the implicits must go in one parameter list.
RANDOM PRODUCTS
object Generic {
type Aux[T, Repr0] = Generic[T] { type Repr = Repr0 }
}
implicit def rProduct[T, Repr](
implicit g: Generic.Aux[T, Repr], rg: Random[Repr]): Random[T] =
() => g.from(rg())
Random[Employee]
// Employee(ltdui,-443373978,false)
case class Foo(a: Employee, b: String)
Random[Foo]
// Diverging implicit expansion!
shapeless solves this problem by using the Aux pattern, which is nowadays a very common technique to expose type members
as type parameters. This finally allows us to have a generic implementation of Random for products.
Unfortunately, the compiler can run into cycles during implicit search, which can gives us a diverging implicit expansion error.
LAZY
Creates a macro that triggers implicit search for types
wrapped in `Lazy` only once.
implicit val rHNil: Random[HNil] = () => HNil
implicit def rHList[H, T <: HList](
implicit rh: Lazy[Random[H]],
rt: Lazy[Random[T]]): Random[H :: T] =
() => rh.value() :: rt.value()
implicit def rProduct[T, Repr <: HList](
implicit g: Generic.Aux[T, Repr],
rg: Lazy[Random[Repr]]): Random[T] =
() => g.from(rg.value())
To help us with diverging implicit expansion errors, shapeless provides us with the Lazy macro. The Lazy macro triggers the
implicit search for a given implicit and if this search triggers searches for types wrapped in Lazy then these will be done only
once and wrapped in a lazy val, which is returned as the corresponding value.
RANDOM COPRODUCTS
sealed trait Receptacle
case class Bottle(a: Int) extends Receptacle
case class Glass(a: String) extends Receptacle
case class Teapot(a: Boolean) extends Receptacle
Generic[Receptacle]
// shapeless.Generic[Receptacle]{
// type Repr = Bottle :+: Glass :+: Teapot :+: shapeless.CNil}
val g = Generic[Receptacle]
g.to(Glass("a"))
// Inr(Inl(Glass(a)))
g.to(Bottle(1))
// Inl(Bottle(1))
g.to(Teapot(true))
// Inr(Inr(Inl(Teapot(true))))
shapeless can also help us with coproducts (i.e. sealed families of case classes). The Generic Repr of coproducts is a
Coproduct instead of an HList. Coproduct is defined in terms of Inr (which doesn't have a value and thus defers to the tail) and
Inl (which has a value and nothing else, thus guaranteeing that there's only one Inl in a Coproduct).
RANDOM COPRODUCTS
case class CoproductOptions[A, C <: Coproduct](
options: List[() => A] = Nil)
implicit def rCNil[A]: CoproductOptions[A, CNil] =
CoproductOptions[A, CNil](Nil)
implicit def rCP[A, H <: A, T <: Coproduct](
implicit rh: Lazy[Random[H]],
rt: Lazy[CoproductOptions[A, T]]): CoproductOptions[A, H :+: T] =
CoproductOptions[A, H :+: T](rh.value :: rt.value.options)
implicit def rCoproduct[T, Repr <: Coproduct](
implicit g: Generic.Aux[T, Repr],
rg: Lazy[CoproductOptions[T, Repr]]): Random[T] =
() => {
val choices = rg.value.options
choices(util.Random.nextInt(choices.length))()
}
RANDOM COPRODUCTS
Random[Receptacle]
// Bottle(2143179073)
Random[Receptacle]
// Glass(eMaxESFnD)
Random[Receptacle]
// Bottle(1915773318)
Random[Receptacle]
// Glass(4MX5xYi8v)
Random[Receptacle]
// Teapot(false)
SPRAY-JSON
sealed abstract class JsValue
case class JsObject(fields: Map[String, JsValue]) extends JsValue
case class JsArray(elements: Vector[JsValue]) extends JsValue
case class JsString(value: String) extends JsValue
case class JsNumber(value: BigDecimal) extends JsValue
sealed trait JsBoolean extends JsValue
case object JsTrue extends JsBoolean
case object JsFalse extends JsBoolean
case object JsNull extends JsValue
So far we haven't required the names of the fields of ADTs when deriving typeclasses. In order to show how to use them, we'll
be using spray-json as an example. A simplified version of the spray-json AST is defined as this.
JSONFORMAT
trait JsonFormat[T] {
def read(json: JsValue): T
def write(obj: T): JsValue
}
We want to use shapeless to derive instances of the JsonFormat type class.
MORE SHAPELESS
SINGLETON TYPES
"bar".narrow : String("bar") // <: String
42.narrow : Int(42) // <: Int
'foo.narrow : Symbol('foo) // <: Symbol
true.narrow : Boolean(true) // <: Boolean
'a ->> "bar" : String with KeyTag[Symbol('a), String]
'b ->> 42 : Int with KeyTag[Symbol('b), Int]
'c ->> true : Boolean with KeyTag[Symbol('c), Boolean]
val a = implicitly[Witness[String("foo")]].value : String("foo")
field[Symbol('a)]("bar") : FieldType[Symbol('a), String]
field[Symbol('b)](42) : FieldType[Symbol('b), Int]
field[Symbol('c)](true) : FieldType[Symbol('c), Boolean]
shapeless introduces the concept of a singleton type, a construction that allows lifting a constant value to a type. The type of a
value that is narrowed is a subtype of the original type, but is refined with a singleton instance of the type. The narrows get
erased at runtime, but allow us to work with them at compile time.
Singleton types are commonly used to add typelevel keys to a given type, and shapeless provides us with utilites to both add
keys and extract keys from a tagged type.
BACK TO HLISTS
('name ->> "foo") :: ('number ->> 42) :: ('manager ->> true) :: HNil
: FieldType[Symbol('name), String] ::
FieldType[Symbol('number), Int] ::
FieldType[Symbol('manager), Boolean] ::
HNil
case class Employee(name: String, number: Int, manager: Boolean)
Using the concept of FieldType previously introduced, we can extend our generic representation of a product to also include the
name of the fields the type is composed of.
implicit object HNilFormat extends JsonFormat[HNil] {
def read(j: JsValue) = HNil
def write(n: HNil) = JsObject()
}
implicit def hListFormat[Key <: Symbol, Value, Remaining <: HList](
implicit
key: Witness.Aux[Key],
jfh: JsonFormat[Value],
jft: JsonFormat[Remaining]
) = new JsonFormat[FieldType[Key, Value] :: Remaining] {
def write(hlist: FieldType[Key, Value] :: Remaining) =
JsObject(jft.write(hlist.tail).asJsObject.fields +
(key.value.name -> jfh.write(hlist.head)))
def read(json: JsValue) = {
val fields = json.asJsObject.fields
val head = jfh.read(fields(key.value.name))
val tail = jft.read(json)
field[Key](head) :: tail
}
}
We can also easily derive an implementation of JsonFormat for HLists of FieldTypes.
val employee =
('name ->> "foo") ::
('number ->> 42) ::
('manager ->> true) :: HNil
employee.toJson
// {"manager":true,"number":42,"name":"foo"}
LABELLEDGENERIC
LabelledGeneric[Employee]
// shapeless.LabelledGeneric[Employee]{
// type Repr = String with KeyTag[Symbol with Tagged[String("name")],String
// Int with KeyTag[Symbol with Tagged[String("number")],Int] ::
// Boolean with KeyTag[Symbol with Tagged[String("manager")],Bo
// = shapeless.LabelledGeneric$$anon$1@5832492c
LabelledGeneric[IceCream]
// shapeless.LabelledGeneric[Employee]{
// type Repr = String with KeyTag[Symbol with Tagged[String("name")],String
// Int with KeyTag[Symbol with Tagged[String("numCherries")],In
// Boolean with KeyTag[Symbol with Tagged[String("inCone")],Boo
// = shapeless.LabelledGeneric$$anon$1@17a5c45a
trait LabelledGeneric[T] {
type Repr
def to(t: T): Repr
def from(r: Repr): T
}
shapeless provides a type class called LabelledGeneric that allows us to go from an algebraic data type to its generic
representation as an HList of FieldTypes (and vice-versa). The LabelledGeneric instance has a type member Repr containing
the type of its generic representation.
LABELLEDGENERIC
val hlist = ('name ->> "foo") ::
('number ->> 42) ::
('manager ->> true) :: HNil
LabelledGeneric[Employee].from(hlist)
// Employee(foo,42,true)
LabelledGeneric[IceCream].from(hlist)
// Does not compile
LABELLEDGENERIC
implicit def productFormat[T, Repr <: HList](
implicit
gen: LabelledGeneric.Aux[T, Repr],
sg: JsonFormat[Repr]
): JsonFormat[T] = new JsonFormat[T] {
def read(j: JsValue): T = gen.from(sg.read(j))
def write(t: T): JsValue = sg.write(gen.to(t))
}
Employee("foo", 42, true).toJson
// {"manager":true,"number":42,"name":"foo"}
Taking this into account, we can derive an implementation of JsonFormat for products if we are able to derive an implementation
of JsonFormat for their generic representation (which we already did for HLists of FieldTypes in a previous slide).
We leave the derivation of JsonFormats for coproducts as an exercise for the reader.
REFERENCES
The Type Astronaut's Guide to Shapeless
spray-json-shapeless
Shapeless for Mortals

More Related Content

What's hot

JavaScript Programming
JavaScript ProgrammingJavaScript Programming
JavaScript ProgrammingSehwan Noh
 
Fundamental JavaScript [UTC, March 2014]
Fundamental JavaScript [UTC, March 2014]Fundamental JavaScript [UTC, March 2014]
Fundamental JavaScript [UTC, March 2014]Aaron Gustafson
 
Javascript basic course
Javascript basic courseJavascript basic course
Javascript basic courseTran Khoa
 
Javascript basics
Javascript basicsJavascript basics
Javascript basicsSolv AS
 
DISE - Windows Based Application Development in Java
DISE - Windows Based Application Development in JavaDISE - Windows Based Application Development in Java
DISE - Windows Based Application Development in JavaRasan Samarasinghe
 
Nikita Popov "What’s new in PHP 8.0?"
Nikita Popov "What’s new in PHP 8.0?"Nikita Popov "What’s new in PHP 8.0?"
Nikita Popov "What’s new in PHP 8.0?"Fwdays
 
Swift - the future of iOS app development
Swift - the future of iOS app developmentSwift - the future of iOS app development
Swift - the future of iOS app developmentopenak
 
What I Love About Ruby
What I Love About RubyWhat I Love About Ruby
What I Love About RubyKeith Bennett
 
Programming Language Swift Overview
Programming Language Swift OverviewProgramming Language Swift Overview
Programming Language Swift OverviewKaz Yoshikawa
 
Java Intro
Java IntroJava Intro
Java Introbackdoor
 
Xbase - Implementing Domain-Specific Languages for Java
Xbase - Implementing Domain-Specific Languages for JavaXbase - Implementing Domain-Specific Languages for Java
Xbase - Implementing Domain-Specific Languages for Javameysholdt
 
javascript objects
javascript objectsjavascript objects
javascript objectsVijay Kalyan
 
Javascript Basics
Javascript BasicsJavascript Basics
Javascript Basicsmsemenistyi
 
Ruby from zero to hero
Ruby from zero to heroRuby from zero to hero
Ruby from zero to heroDiego Lemos
 
JavaScript Growing Up
JavaScript Growing UpJavaScript Growing Up
JavaScript Growing UpDavid Padbury
 
Functional Objects & Function and Closures
Functional Objects  & Function and ClosuresFunctional Objects  & Function and Closures
Functional Objects & Function and ClosuresSandip Kumar
 

What's hot (20)

Javascript essentials
Javascript essentialsJavascript essentials
Javascript essentials
 
JavaScript Programming
JavaScript ProgrammingJavaScript Programming
JavaScript Programming
 
Fundamental JavaScript [UTC, March 2014]
Fundamental JavaScript [UTC, March 2014]Fundamental JavaScript [UTC, March 2014]
Fundamental JavaScript [UTC, March 2014]
 
Javascript basic course
Javascript basic courseJavascript basic course
Javascript basic course
 
Type Checking JavaScript
Type Checking JavaScriptType Checking JavaScript
Type Checking JavaScript
 
Javascript basics
Javascript basicsJavascript basics
Javascript basics
 
DISE - Windows Based Application Development in Java
DISE - Windows Based Application Development in JavaDISE - Windows Based Application Development in Java
DISE - Windows Based Application Development in Java
 
DITEC - Programming with Java
DITEC - Programming with JavaDITEC - Programming with Java
DITEC - Programming with Java
 
Nikita Popov "What’s new in PHP 8.0?"
Nikita Popov "What’s new in PHP 8.0?"Nikita Popov "What’s new in PHP 8.0?"
Nikita Popov "What’s new in PHP 8.0?"
 
Swift - the future of iOS app development
Swift - the future of iOS app developmentSwift - the future of iOS app development
Swift - the future of iOS app development
 
What I Love About Ruby
What I Love About RubyWhat I Love About Ruby
What I Love About Ruby
 
Programming Language Swift Overview
Programming Language Swift OverviewProgramming Language Swift Overview
Programming Language Swift Overview
 
Java Intro
Java IntroJava Intro
Java Intro
 
Xbase - Implementing Domain-Specific Languages for Java
Xbase - Implementing Domain-Specific Languages for JavaXbase - Implementing Domain-Specific Languages for Java
Xbase - Implementing Domain-Specific Languages for Java
 
javascript objects
javascript objectsjavascript objects
javascript objects
 
Javascript Basics
Javascript BasicsJavascript Basics
Javascript Basics
 
Ruby from zero to hero
Ruby from zero to heroRuby from zero to hero
Ruby from zero to hero
 
Scala - brief intro
Scala - brief introScala - brief intro
Scala - brief intro
 
JavaScript Growing Up
JavaScript Growing UpJavaScript Growing Up
JavaScript Growing Up
 
Functional Objects & Function and Closures
Functional Objects  & Function and ClosuresFunctional Objects  & Function and Closures
Functional Objects & Function and Closures
 

Similar to Automatic Type Class Derivation with Shapeless

Shapeless- Generic programming for Scala
Shapeless- Generic programming for ScalaShapeless- Generic programming for Scala
Shapeless- Generic programming for ScalaKnoldus Inc.
 
Go Beyond Higher Order Functions: A Journey into Functional Programming
Go Beyond Higher Order Functions: A Journey into Functional ProgrammingGo Beyond Higher Order Functions: A Journey into Functional Programming
Go Beyond Higher Order Functions: A Journey into Functional ProgrammingLex Sheehan
 
Milano JS Meetup - Gabriele Petronella - Codemotion Milan 2016
Milano JS Meetup -  Gabriele Petronella - Codemotion Milan 2016Milano JS Meetup -  Gabriele Petronella - Codemotion Milan 2016
Milano JS Meetup - Gabriele Petronella - Codemotion Milan 2016Codemotion
 
Type script - advanced usage and practices
Type script  - advanced usage and practicesType script  - advanced usage and practices
Type script - advanced usage and practicesIwan van der Kleijn
 
Fp in scala part 1
Fp in scala part 1Fp in scala part 1
Fp in scala part 1Hang Zhao
 
Effective Scala (JavaDay Riga 2013)
Effective Scala (JavaDay Riga 2013)Effective Scala (JavaDay Riga 2013)
Effective Scala (JavaDay Riga 2013)mircodotta
 
“Insulin” for Scala’s Syntactic Diabetes
“Insulin” for Scala’s Syntactic Diabetes“Insulin” for Scala’s Syntactic Diabetes
“Insulin” for Scala’s Syntactic DiabetesTzach Zohar
 
Why is Haskell so hard! (And how to deal with it?)
Why is Haskell so hard! (And how to deal with it?)Why is Haskell so hard! (And how to deal with it?)
Why is Haskell so hard! (And how to deal with it?)Saurabh Nanda
 
C++ Standard Template Library
C++ Standard Template LibraryC++ Standard Template Library
C++ Standard Template LibraryIlio Catallo
 
Scala collections api expressivity and brevity upgrade from java
Scala collections api  expressivity and brevity upgrade from javaScala collections api  expressivity and brevity upgrade from java
Scala collections api expressivity and brevity upgrade from javaIndicThreads
 
Back to the Future with TypeScript
Back to the Future with TypeScriptBack to the Future with TypeScript
Back to the Future with TypeScriptAleš Najmann
 
Fp in scala part 2
Fp in scala part 2Fp in scala part 2
Fp in scala part 2Hang Zhao
 
Ceylon idioms by Gavin King
Ceylon idioms by Gavin KingCeylon idioms by Gavin King
Ceylon idioms by Gavin KingUnFroMage
 
Metaprogramming in Scala 2.10, Eugene Burmako,
Metaprogramming  in Scala 2.10, Eugene Burmako, Metaprogramming  in Scala 2.10, Eugene Burmako,
Metaprogramming in Scala 2.10, Eugene Burmako, Vasil Remeniuk
 
Haskell retrospective
Haskell retrospectiveHaskell retrospective
Haskell retrospectivechenge2k
 
Introduction to Functional Languages
Introduction to Functional LanguagesIntroduction to Functional Languages
Introduction to Functional Languagessuthi
 
03 introduction to graph databases
03   introduction to graph databases03   introduction to graph databases
03 introduction to graph databasesNeo4j
 
Generic Types in Java (for ArtClub @ArtBrains Software)
Generic Types in Java (for ArtClub @ArtBrains Software)Generic Types in Java (for ArtClub @ArtBrains Software)
Generic Types in Java (for ArtClub @ArtBrains Software)Andrew Petryk
 

Similar to Automatic Type Class Derivation with Shapeless (20)

Shapeless- Generic programming for Scala
Shapeless- Generic programming for ScalaShapeless- Generic programming for Scala
Shapeless- Generic programming for Scala
 
Go Beyond Higher Order Functions: A Journey into Functional Programming
Go Beyond Higher Order Functions: A Journey into Functional ProgrammingGo Beyond Higher Order Functions: A Journey into Functional Programming
Go Beyond Higher Order Functions: A Journey into Functional Programming
 
Milano JS Meetup - Gabriele Petronella - Codemotion Milan 2016
Milano JS Meetup -  Gabriele Petronella - Codemotion Milan 2016Milano JS Meetup -  Gabriele Petronella - Codemotion Milan 2016
Milano JS Meetup - Gabriele Petronella - Codemotion Milan 2016
 
Type script - advanced usage and practices
Type script  - advanced usage and practicesType script  - advanced usage and practices
Type script - advanced usage and practices
 
Fancy talk
Fancy talkFancy talk
Fancy talk
 
Fp in scala part 1
Fp in scala part 1Fp in scala part 1
Fp in scala part 1
 
Effective Scala (JavaDay Riga 2013)
Effective Scala (JavaDay Riga 2013)Effective Scala (JavaDay Riga 2013)
Effective Scala (JavaDay Riga 2013)
 
“Insulin” for Scala’s Syntactic Diabetes
“Insulin” for Scala’s Syntactic Diabetes“Insulin” for Scala’s Syntactic Diabetes
“Insulin” for Scala’s Syntactic Diabetes
 
Why is Haskell so hard! (And how to deal with it?)
Why is Haskell so hard! (And how to deal with it?)Why is Haskell so hard! (And how to deal with it?)
Why is Haskell so hard! (And how to deal with it?)
 
C++ Standard Template Library
C++ Standard Template LibraryC++ Standard Template Library
C++ Standard Template Library
 
Scala collections api expressivity and brevity upgrade from java
Scala collections api  expressivity and brevity upgrade from javaScala collections api  expressivity and brevity upgrade from java
Scala collections api expressivity and brevity upgrade from java
 
Back to the Future with TypeScript
Back to the Future with TypeScriptBack to the Future with TypeScript
Back to the Future with TypeScript
 
Chapter 08
Chapter 08Chapter 08
Chapter 08
 
Fp in scala part 2
Fp in scala part 2Fp in scala part 2
Fp in scala part 2
 
Ceylon idioms by Gavin King
Ceylon idioms by Gavin KingCeylon idioms by Gavin King
Ceylon idioms by Gavin King
 
Metaprogramming in Scala 2.10, Eugene Burmako,
Metaprogramming  in Scala 2.10, Eugene Burmako, Metaprogramming  in Scala 2.10, Eugene Burmako,
Metaprogramming in Scala 2.10, Eugene Burmako,
 
Haskell retrospective
Haskell retrospectiveHaskell retrospective
Haskell retrospective
 
Introduction to Functional Languages
Introduction to Functional LanguagesIntroduction to Functional Languages
Introduction to Functional Languages
 
03 introduction to graph databases
03   introduction to graph databases03   introduction to graph databases
03 introduction to graph databases
 
Generic Types in Java (for ArtClub @ArtBrains Software)
Generic Types in Java (for ArtClub @ArtBrains Software)Generic Types in Java (for ArtClub @ArtBrains Software)
Generic Types in Java (for ArtClub @ArtBrains Software)
 

Recently uploaded

why an Opensea Clone Script might be your perfect match.pdf
why an Opensea Clone Script might be your perfect match.pdfwhy an Opensea Clone Script might be your perfect match.pdf
why an Opensea Clone Script might be your perfect match.pdfjoe51371421
 
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...gurkirankumar98700
 
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...MyIntelliSource, Inc.
 
Professional Resume Template for Software Developers
Professional Resume Template for Software DevelopersProfessional Resume Template for Software Developers
Professional Resume Template for Software DevelopersVinodh Ram
 
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...stazi3110
 
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...harshavardhanraghave
 
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer DataAdobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer DataBradBedford3
 
Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...OnePlan Solutions
 
A Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxA Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxComplianceQuest1
 
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsUnveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsAlberto González Trastoy
 
Cloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStackCloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStackVICTOR MAESTRE RAMIREZ
 
What is Binary Language? Computer Number Systems
What is Binary Language?  Computer Number SystemsWhat is Binary Language?  Computer Number Systems
What is Binary Language? Computer Number SystemsJheuzeDellosa
 
HR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comHR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comFatema Valibhai
 
TECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providerTECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providermohitmore19
 
Hand gesture recognition PROJECT PPT.pptx
Hand gesture recognition PROJECT PPT.pptxHand gesture recognition PROJECT PPT.pptx
Hand gesture recognition PROJECT PPT.pptxbodapatigopi8531
 
Building Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop SlideBuilding Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop SlideChristina Lin
 
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfThe Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfkalichargn70th171
 
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...ICS
 

Recently uploaded (20)

why an Opensea Clone Script might be your perfect match.pdf
why an Opensea Clone Script might be your perfect match.pdfwhy an Opensea Clone Script might be your perfect match.pdf
why an Opensea Clone Script might be your perfect match.pdf
 
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
 
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
 
Professional Resume Template for Software Developers
Professional Resume Template for Software DevelopersProfessional Resume Template for Software Developers
Professional Resume Template for Software Developers
 
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
 
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
 
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer DataAdobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
 
Vip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS Live
Vip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS LiveVip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS Live
Vip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS Live
 
Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...
 
A Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxA Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docx
 
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsUnveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
 
Exploring iOS App Development: Simplifying the Process
Exploring iOS App Development: Simplifying the ProcessExploring iOS App Development: Simplifying the Process
Exploring iOS App Development: Simplifying the Process
 
Cloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStackCloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStack
 
What is Binary Language? Computer Number Systems
What is Binary Language?  Computer Number SystemsWhat is Binary Language?  Computer Number Systems
What is Binary Language? Computer Number Systems
 
HR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comHR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.com
 
TECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providerTECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service provider
 
Hand gesture recognition PROJECT PPT.pptx
Hand gesture recognition PROJECT PPT.pptxHand gesture recognition PROJECT PPT.pptx
Hand gesture recognition PROJECT PPT.pptx
 
Building Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop SlideBuilding Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
 
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfThe Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
 
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
 

Automatic Type Class Derivation with Shapeless

  • 1. AUTOMATIC TYPE CLASS DERIVATION WITH SHAPELESS Shi Forward Tech Talks July 6, 2017 / Joao Azevedo The purpose of this talk is to give the basics on how to do automatic type class derivation using Shapeless. At the end of the talk, attendees should be aware of the building blocks Shapeless provides and be able to apply some of the described patterns to derive their own type classes.
  • 3. TYPE CLASSES A definition of behaviour in the form of operations that must be supported by a given type. A way to implement ad hoc polymorphism.
  • 4. TYPE CLASSES def mean[T](xs: Vector[T])(implicit ev: NumberLike[T]): T = ev.divide(xs.reduce(ev.plus(_, _)), xs.size)
  • 5. SHAPELESS You must be shapeless, formless, like water. When you pour water in a cup, it becomes the cup. When you pour water in a bottle, it becomes the bottle. When you pour water in a teapot, it becomes the teapot. Water can drip and it can crash. Become like water my friend. -- Bruce Lee
  • 6. SHAPELESS A type class and dependent type based generic programming library for Scala. Generic programming provides ways to exploit similarities between types to avoid repetition. case class Employee(name: String, number: Int, manager: Boolean) case class IceCream(name: String, numCherries: Int, inCone: Boolean) Sometimes, types are too specific and we want to explore similarities in their shape to avoid repetition. In the presented example, even though Employee and IceCream are different types, they share the same shape: they both contain three fields of the same types: String, Int and Boolean.
  • 7. A RANDOM EXAMPLE We start with a practical example to show some of the shapeless concepts that are relevant for automatic derivation of type classes.
  • 8. A RANDOM EXAMPLE trait Random[A] extends Function0[A] object Random { def apply[A](implicit r: Random[A]) = r() } Random[Int] // 578073111 Random[Double] // 0.48802405390668835 Random[String] // mbbHlZAam Random[Employee] // Employee(Y7F,1976420522,true) Random[IceCream] // IceCream(CywOmpJd,-335712437,false) The idea is to have a Random typeclass that is capable of producing "random" instances of a given type.
  • 9. A RANDOM EXAMPLE implicit val rInt: Random[Int] = () => util.Random.nextInt implicit val rDouble: Random[Double] = () => util.Random.nextDouble implicit val rString: Random[String] = () => util.Random.alphanumeric.take( util.Random.nextInt(10)).mkString implicit val rBoolean: Random[Boolean] = () => util.Random.nextBoolean We can start by implementing Random instances for some base types. The String implementation is obviously incorrect, but the specific implementation is not very relevant for now. We just want to make sure we have a typeclass for some base types.
  • 10. A RANDOM EXAMPLE implicit def rEmployee(implicit rs: Random[String], ri: Random[Int], rb: Random[Boolean]): Random[Employee] = () => Employee(rs(), ri(), rb()) implicit def rIceCream(implicit rs: Random[String], ri: Random[Int], rb: Random[Boolean]): Random[IceCream] = () => IceCream(rs(), ri(), rb()) Having implementations of Random for some base types, we can have implementations of Random for more complex types that base themselves on the Random implementations for the types of fields that compose those types. However, we still have two distinct implementations for Employee and IceCream even though they're remarkably similar. Can we do better?
  • 11. SHAPELESS TO THE RESCUE! shapeless allows us to do better by taking advantage of the shape of the types and exploiting similarities between shapes.
  • 12. HLISTS "hello" :: 13 :: true :: HNil : String :: Int :: Boolean :: HNil The first important concept from shapeless that is very relevant for the purpose of automatic derivation of type classes are HLists. HLists are a generic representation of products, much like Scala's built-in tuples. They're a bit more powerful than Scala's tuples because each size of tuple has an unrelated type and we can't represent 0-length tuples. HLists are similar to Scala's Lists, but, at compile time, we know both their size and the type of each element that composes the list.
  • 13. RANDOM HLISTS implicit val rHNil: Random[HNil] = () => HNil implicit def rHList[H, T <: HList]( implicit rh: Random[H], rt: Random[T]): Random[H :: T] = () => rh() :: rt() Knowing what HLists are, we can create an implementation of Random for HLists, provided that we have implementations of Random for the type of each element that the list is composed of. This already provides us with a generic implementation of Random for products.
  • 14. GENERIC Generic[Employee] // shapeless.Generic[Employee]{ // type Repr = String :: Int :: Boolean :: shapeless.HNil} = // anon$macro$16$1@3f09ba38 Generic[IceCream] // shapeless.Generic[IceCream]{ // type Repr = String :: Int :: Boolean :: shapeless.HNil} = // anon$macro$12$1@750eba8f trait Generic[T] { type Repr def to(t: T): Repr def from(r: Repr): T } shapeless provides a type class called Generic that allows us to go from an algebraic data type to its generic representation as an HList (and vice-versa). The Generic instance has a type member Repr containing the type of its generic representation.
  • 15. GENERIC val hlist = "a" :: 1 :: true :: HNil Generic[Employee].from(hlist) // Employee(a,1,true) Generic[IceCream].from(hlist) // IceCream(a,1,true) Using Generic, we can take advantage of the shape of an ADT and convert back and forth from different ADTs if they have the same Repr.
  • 16. RANDOM PRODUCTS implicit def rProduct[T]( implicit g: Generic[T], rg: Random[g.Repr]): Random[T] = () => g.from(rg()) Unfortunately this doesn't compile. Taking this into account, we can derive an implementation of Random for products if we are able to derive an implementation of Random for their generic representation (which we already did for HLists in a previous slide). Unfortunately this doesn't compile because we can't reference a type member of a parameter within the same parameter list the parameter is in, and all the implicits must go in one parameter list.
  • 17. RANDOM PRODUCTS object Generic { type Aux[T, Repr0] = Generic[T] { type Repr = Repr0 } } implicit def rProduct[T, Repr]( implicit g: Generic.Aux[T, Repr], rg: Random[Repr]): Random[T] = () => g.from(rg()) Random[Employee] // Employee(ltdui,-443373978,false) case class Foo(a: Employee, b: String) Random[Foo] // Diverging implicit expansion! shapeless solves this problem by using the Aux pattern, which is nowadays a very common technique to expose type members as type parameters. This finally allows us to have a generic implementation of Random for products. Unfortunately, the compiler can run into cycles during implicit search, which can gives us a diverging implicit expansion error.
  • 18. LAZY Creates a macro that triggers implicit search for types wrapped in `Lazy` only once. implicit val rHNil: Random[HNil] = () => HNil implicit def rHList[H, T <: HList]( implicit rh: Lazy[Random[H]], rt: Lazy[Random[T]]): Random[H :: T] = () => rh.value() :: rt.value() implicit def rProduct[T, Repr <: HList]( implicit g: Generic.Aux[T, Repr], rg: Lazy[Random[Repr]]): Random[T] = () => g.from(rg.value()) To help us with diverging implicit expansion errors, shapeless provides us with the Lazy macro. The Lazy macro triggers the implicit search for a given implicit and if this search triggers searches for types wrapped in Lazy then these will be done only once and wrapped in a lazy val, which is returned as the corresponding value.
  • 19. RANDOM COPRODUCTS sealed trait Receptacle case class Bottle(a: Int) extends Receptacle case class Glass(a: String) extends Receptacle case class Teapot(a: Boolean) extends Receptacle Generic[Receptacle] // shapeless.Generic[Receptacle]{ // type Repr = Bottle :+: Glass :+: Teapot :+: shapeless.CNil} val g = Generic[Receptacle] g.to(Glass("a")) // Inr(Inl(Glass(a))) g.to(Bottle(1)) // Inl(Bottle(1)) g.to(Teapot(true)) // Inr(Inr(Inl(Teapot(true)))) shapeless can also help us with coproducts (i.e. sealed families of case classes). The Generic Repr of coproducts is a Coproduct instead of an HList. Coproduct is defined in terms of Inr (which doesn't have a value and thus defers to the tail) and Inl (which has a value and nothing else, thus guaranteeing that there's only one Inl in a Coproduct).
  • 20. RANDOM COPRODUCTS case class CoproductOptions[A, C <: Coproduct]( options: List[() => A] = Nil) implicit def rCNil[A]: CoproductOptions[A, CNil] = CoproductOptions[A, CNil](Nil) implicit def rCP[A, H <: A, T <: Coproduct]( implicit rh: Lazy[Random[H]], rt: Lazy[CoproductOptions[A, T]]): CoproductOptions[A, H :+: T] = CoproductOptions[A, H :+: T](rh.value :: rt.value.options) implicit def rCoproduct[T, Repr <: Coproduct]( implicit g: Generic.Aux[T, Repr], rg: Lazy[CoproductOptions[T, Repr]]): Random[T] = () => { val choices = rg.value.options choices(util.Random.nextInt(choices.length))() }
  • 21. RANDOM COPRODUCTS Random[Receptacle] // Bottle(2143179073) Random[Receptacle] // Glass(eMaxESFnD) Random[Receptacle] // Bottle(1915773318) Random[Receptacle] // Glass(4MX5xYi8v) Random[Receptacle] // Teapot(false)
  • 22. SPRAY-JSON sealed abstract class JsValue case class JsObject(fields: Map[String, JsValue]) extends JsValue case class JsArray(elements: Vector[JsValue]) extends JsValue case class JsString(value: String) extends JsValue case class JsNumber(value: BigDecimal) extends JsValue sealed trait JsBoolean extends JsValue case object JsTrue extends JsBoolean case object JsFalse extends JsBoolean case object JsNull extends JsValue So far we haven't required the names of the fields of ADTs when deriving typeclasses. In order to show how to use them, we'll be using spray-json as an example. A simplified version of the spray-json AST is defined as this.
  • 23. JSONFORMAT trait JsonFormat[T] { def read(json: JsValue): T def write(obj: T): JsValue } We want to use shapeless to derive instances of the JsonFormat type class.
  • 25. SINGLETON TYPES "bar".narrow : String("bar") // <: String 42.narrow : Int(42) // <: Int 'foo.narrow : Symbol('foo) // <: Symbol true.narrow : Boolean(true) // <: Boolean 'a ->> "bar" : String with KeyTag[Symbol('a), String] 'b ->> 42 : Int with KeyTag[Symbol('b), Int] 'c ->> true : Boolean with KeyTag[Symbol('c), Boolean] val a = implicitly[Witness[String("foo")]].value : String("foo") field[Symbol('a)]("bar") : FieldType[Symbol('a), String] field[Symbol('b)](42) : FieldType[Symbol('b), Int] field[Symbol('c)](true) : FieldType[Symbol('c), Boolean] shapeless introduces the concept of a singleton type, a construction that allows lifting a constant value to a type. The type of a value that is narrowed is a subtype of the original type, but is refined with a singleton instance of the type. The narrows get erased at runtime, but allow us to work with them at compile time. Singleton types are commonly used to add typelevel keys to a given type, and shapeless provides us with utilites to both add keys and extract keys from a tagged type.
  • 26. BACK TO HLISTS ('name ->> "foo") :: ('number ->> 42) :: ('manager ->> true) :: HNil : FieldType[Symbol('name), String] :: FieldType[Symbol('number), Int] :: FieldType[Symbol('manager), Boolean] :: HNil case class Employee(name: String, number: Int, manager: Boolean) Using the concept of FieldType previously introduced, we can extend our generic representation of a product to also include the name of the fields the type is composed of.
  • 27. implicit object HNilFormat extends JsonFormat[HNil] { def read(j: JsValue) = HNil def write(n: HNil) = JsObject() } implicit def hListFormat[Key <: Symbol, Value, Remaining <: HList]( implicit key: Witness.Aux[Key], jfh: JsonFormat[Value], jft: JsonFormat[Remaining] ) = new JsonFormat[FieldType[Key, Value] :: Remaining] { def write(hlist: FieldType[Key, Value] :: Remaining) = JsObject(jft.write(hlist.tail).asJsObject.fields + (key.value.name -> jfh.write(hlist.head))) def read(json: JsValue) = { val fields = json.asJsObject.fields val head = jfh.read(fields(key.value.name)) val tail = jft.read(json) field[Key](head) :: tail } } We can also easily derive an implementation of JsonFormat for HLists of FieldTypes.
  • 28. val employee = ('name ->> "foo") :: ('number ->> 42) :: ('manager ->> true) :: HNil employee.toJson // {"manager":true,"number":42,"name":"foo"}
  • 29. LABELLEDGENERIC LabelledGeneric[Employee] // shapeless.LabelledGeneric[Employee]{ // type Repr = String with KeyTag[Symbol with Tagged[String("name")],String // Int with KeyTag[Symbol with Tagged[String("number")],Int] :: // Boolean with KeyTag[Symbol with Tagged[String("manager")],Bo // = shapeless.LabelledGeneric$$anon$1@5832492c LabelledGeneric[IceCream] // shapeless.LabelledGeneric[Employee]{ // type Repr = String with KeyTag[Symbol with Tagged[String("name")],String // Int with KeyTag[Symbol with Tagged[String("numCherries")],In // Boolean with KeyTag[Symbol with Tagged[String("inCone")],Boo // = shapeless.LabelledGeneric$$anon$1@17a5c45a trait LabelledGeneric[T] { type Repr def to(t: T): Repr def from(r: Repr): T } shapeless provides a type class called LabelledGeneric that allows us to go from an algebraic data type to its generic representation as an HList of FieldTypes (and vice-versa). The LabelledGeneric instance has a type member Repr containing the type of its generic representation.
  • 30. LABELLEDGENERIC val hlist = ('name ->> "foo") :: ('number ->> 42) :: ('manager ->> true) :: HNil LabelledGeneric[Employee].from(hlist) // Employee(foo,42,true) LabelledGeneric[IceCream].from(hlist) // Does not compile
  • 31. LABELLEDGENERIC implicit def productFormat[T, Repr <: HList]( implicit gen: LabelledGeneric.Aux[T, Repr], sg: JsonFormat[Repr] ): JsonFormat[T] = new JsonFormat[T] { def read(j: JsValue): T = gen.from(sg.read(j)) def write(t: T): JsValue = sg.write(gen.to(t)) } Employee("foo", 42, true).toJson // {"manager":true,"number":42,"name":"foo"} Taking this into account, we can derive an implementation of JsonFormat for products if we are able to derive an implementation of JsonFormat for their generic representation (which we already did for HLists of FieldTypes in a previous slide). We leave the derivation of JsonFormats for coproducts as an exercise for the reader.
  • 32. REFERENCES The Type Astronaut's Guide to Shapeless spray-json-shapeless Shapeless for Mortals