Demystifying Type Class
Derivation in Shapeless
1 / 22
//
Serialize case class?
CSV/JSON/XML/...
case class User(id: Long, name: String, active: Boolean)
...
def toCSV[T](t: T) = ?
2 / 22
Serialize case class?
CSV/JSON/XML/...
case class User(id: Long, name: String, active: Boolean)
...
def toCSV(u: User) = s"${u.id},{u.name},{u.active}"
Result:
scala> toCSV(User(1L, "test", true))
res1: String = 1,test,true
2 / 22
Serialize case class?
CSV/JSON/XML/Whatever
Result:
scala> toCSV(User(1L, "test", Admin(1L), Address("Wall str", 192), 1.0))
res1: String = 1, test, 1, Wall str, 192, 1.0
case class User(id: Long, name: String, role: Role, addr: Address, score: Double)
sealed trait Role
case class Address(street: String, house: Int)
...
def toCSV(u: User) = s"${u.id}, ${u.name}, ${toCSV(u.role)}, ${toCSV(u.addr)}, ${u
def toCSV(r: Role) = r match { case a: Admin => s"${a.id}, "; case c; Client => s"$
def toCSV(r: Address) = s"${a.street}, ${a.house}"
2 / 22
Problem
def toCSV[T](t: T): String = ???
3 / 22
3 / 22
Agenda
Basics: ADTs, Products & Coproducts
Type Class pattern
Generic Derivation
[live-code]
Lazy, Aux
Debugging
4 / 22
How do we model our domain?
5 / 22
case class /
sealed trait
case class
sealed trait
case class User(id: Long, name: String,
active: Boolean, role: Role)
sealed trait Role
case class Admin(id: Long, special: Boolean) extends Role
case class Client(id: Long) extends Role
How do we model our domain?
5 / 22
case class /
sealed trait
TupleN / Either
TupleN[...] extends Product
scala.Either / cats.Xor / scalaz./
type User = (Long, String, Boolean, Role)
type Role = Either[Admin, Client]
type Admin = (Long, Boolean)
type Client = Long
How do we model our domain?
5 / 22
case class /
sealed trait
TupleN / Either
TupleN[...] extends Product
scala.Either / cats.Xor / scalaz./
type User = (Long, String, Boolean, Role)
type Role = Either[Admin, Client]
type Admin = (Long, Boolean)
type Client = Long
How do we model our domain?
5 / 22
case class /
sealed trait
TupleN / Either
HList /
Coproduct
HList
Coproduct
type User = Long :: String :: Boolean :: Role :: HNil
type Role = Admin :+: Client :+: CNil
type Admin = Long :: Boolean :: HNil
type Client = Long :: HNil
Abstracting over arity
val admin: Role = Inl(2L :: true :: HNil)
val sandy: User = 1 :: "Sandy" :: true :: admin :: HNil
// res5: User = 1 :: Sandy :: true :: Inl(2 :: true :: HNil)
5 / 22
AND types
case class
Tuple
HList
OR types
sealed trait
Either
Coproduct
ADT *
case class User(id: Long, name: String, active: Boolean, role: Role)
sealed trait Role
case class Admin(id: Long, special: Boolean) extends Role
case class Client(id: Long) extends Role
* Stands for Algebraic Data Type
6 / 22
HList
val hlist = 1 :: "one" :: 1L :: HNil
// res0: shapeless.::[
// Int,shapeless.::[
// String,shapeless.::[Long,
// shapeless.HNil
// ]
// ]
// ] = 1 :: "one" :: 1L :: HNil
hlist.head // Int
hlist.tail.head // String
hlist.tail.tail.head // Long
val s = hlist.select[String] // returns "one".
demo.select[List[Int]] // Compilation error. demo does not contain a List[Int]
take, head, tail, map, flatMap, zip
7 / 22
HList
De nition:
sealed trait HList
case class ::[H, T <: HList](head : H, tail : T) extends HList // HCons
case object HNil extends HList
7 / 22
HList
De nition:
sealed trait HList
case class ::[H, T <: HList](head : H, tail : T) extends HList // HCons
case object HNil extends HList
List De nition:
sealed trait List[+A]
case class ::[A](head: A, tail: List[A]) extends List[A]
case object Nil extends List[Nothing]
7 / 22
Coproduct
Once you feel comfortable with an HList - for Coproduct it is quite similar
sealed trait Coproduct
sealed trait :+:[+H, +T <: Coproduct] extends Coproduct
case class Inl[+H, +T <: Coproduct](head : H) extends :+:[H, T]
case class Inr[+H, +T <: Coproduct](tail : T) extends :+:[H, T]
sealed trait CNil extends Coproduct
Usage:
// 'kind-of' Either[Int, String, Boolean]
type Co = Int :+: String :+: Boolean :+: CNil
val co1 = Inl(42L) // Int :+: String :+: Boolean :+: CNil
val co2 = Inr(Inl("forty-two") // Int :+: String :+: Boolean :+: CNil
val co3 = Inr(Inr(Inl(true)) // Int :+: String :+: Boolean :+: CNil
8 / 22
shapeless.Generic
8 / 22
Generic
import shapeless.Generic
case class User(id: Long, name: String, active: Boolean)
val generic = Generic[User]
// res0: shapeless.Generic[User]{type Repr = shapeless.::[Long,
// shapeless.::[String,
// shapeless.::[Boolean,
// shapeless.HNil
// ]
// ]
//]}
9 / 22
Generic
trait Generic[A] {
type Repr
def to(value: A): Repr
def from(value: Repr): A
}
Usage:
scala> val user = User(1L, "josh", true)
user: User = User(1,josh,true)
scala> generic.to(user)
res2: res1.Repr = 1 :: josh :: true :: HNil
scala> generic.from(res2)
res3: User = User(1,josh,true)
9 / 22
Generic
trait Generic[A] {
type Repr
def to(value: A): Repr
def from(value: Repr): A
}
Usage:
scala> val user = User(1L, "josh", true)
user: User = User(1,josh,true)
scala> generic.to(user)
res2: res1.Repr = 1 :: josh :: true :: HNil
scala> generic.from(res2)
res3: User = User(1,josh,true)
9 / 22
Type Class
pattern
9 / 22
Type Class pattern
scala standard library
- Numeric (Collection's sum, product, min, max)
- Ordering (Collection's sorted)
- CanBuildFrom (whole collections api)
- IsSeqLike
- IsTraversableOnce
Cats, Scalaz (all of the functional abstractions like Functor, Applicative,
Monad, ...)
10 / 22
scala.Ordering /**
* Ordering is a trait whose instances each represent
* a strategy for sorting instances of a type.
* ...
*/
trait Ordering[T] extends Comparator[T] {
/**
* Returns an integer whose sign communicates
* how x compares to y.
*/
def compare(x: T, y: T): Int
}
Type Class pattern
11 / 22
scala.Ordering
De nition
trait Ordering[T] extends Comparator[T] {
def compare(x: T, y: T): Int
}
Type Class pattern
11 / 22
scala.Ordering
De nition
Instances
trait Ordering[T] extends Comparator[T] {
def compare(x: T, y: T): Int
}
Type Class pattern
object Ordering {
implicit val intOrd: Ordering[Int] = new Ordering[Int] {
def compare(x: Int, y: Int) = lang.Integer.compare(x, y)
}
implicit val longOrd: Ordering[Long] = new Ordering[Long]
def compare(x: Long, y: Long) = lang.Long.compare(x, y)
}
...
}
11 / 22
scala.Ordering
De nition
Instances
implicit
parameter
trait Ordering[T] extends Comparator[T] {
def compare(x: T, y: T): Int
}
def sorted[T](implicit ord: Ordering[T]): Repr
..or context bound
def sorted[T: Ordering]: Repr
Type Class pattern
object Ordering {
implicit val intOrd: Ordering[Int] = new Ordering[Int] {
def compare(x: Int, y: Int) = lang.Integer.compare(x, y)
}
implicit val longOrd: Ordering[Long] = new Ordering[Long]
def compare(x: Long, y: Long) = lang.Long.compare(x, y)
}
...
}
11 / 22
scala.Ordering
De nition
Instances
implicit
parameter
Usage:
Type Class pattern
List(1, 3, 2).sorted
// at this point compiler is searching implicit value
// in the ?global?, local scope, imported scope, companion o
12 / 22
Type Class pattern
https://github.com/mpilquist/simulacrum
@typeclass trait CSVSerializer[A] {
@op("§") def serialize(a: A): String
}
13 / 22
Type Class pattern
https://github.com/mpilquist/simulacrum
@typeclass trait CSVSerializer[A] {
@op("§") def serialize(a: A): String
}
trait CSVSerializer[A] {
def serialize(x: A, y: A): A
}
object CSVSerializer {
def apply[A](implicit instance: CSVSerializer[A]): CSVSerializer[A] = instance
trait Ops[A] {
def typeClassInstance: CSVSerializer[A]
def self: A
def §(y: A): A = typeClassInstance.append(self, y)
}
trait ToCSVSerializerOps {
implicit def toCSVSerializerOps[A](target: A)(implicit tc: CSVSerializer[A]): O
val self = target
val typeClassInstance = tc
}
}
...
13 / 22
CSV Serializer
Type Class de nition
trait CSVSerializer[A] {
def serialize(a: A): List[String]
}
14 / 22
CSV Serializer
Type Class de nition
trait CSVSerializer[A] {
def serialize(a: A): List[String]
}
lets de ne some helpers..
object CSVSerializer {
def apply[A](implicit serializer: CSVSerializer[A]): CSVSerializer[A] =
serializer
implicit class WithSerialize[A](a: A) {
def toCSV(implicit serializer: CSVSerializer[A]) =
serializer.serialize(a).mkString(",")
}
}
14 / 22
deriving serializer
[live-code]
14 / 22
Aux Pattern
scala> trait Foo { type T }
// defined trait Foo
scala> def f(foo: Foo, t: foo.T) = ???
<console>:13: error: illegal dependent method type: parameter may only be
referenced in a subsequent parameter section
def f(foo: Foo, t: foo.T) = ???
^
scala>
15 / 22
Aux Pattern
scala> trait Foo { type T }
// defined trait Foo
scala> def f(foo: Foo, t: foo.T) = ???
<console>:13: error: illegal dependent method type: parameter may only be
referenced in a subsequent parameter section
def f(foo: Foo, t: foo.T) = ???
^
scala>
Solution:
scala> trait Foo { type T }
// defined trait Foo
scala> type Aux[T0] = Foo { type T = T0 }
// defined type alias Aux
scala> def f[T](foo: Aux[T], t: T) = ???
// f: [T](foo: Aux[T], t: T)Nothing
15 / 22
Aux Pattern
type Aux[T, Repr0] = Generic[T] { type Repr = Repr0 }
16 / 22
Implicit divergence
nested structure
case class Account(id: Long, user: User)
case class User(id: Long, name: String, active: Boolean)
/*1*/ CSVSerializer[Account]
/*2*/ CSVSerializer[::[Long, ::[User, HNil]]]
/*3*/ CSVSerializer[::[User, HNil]]
/*4*/ CSVSerializer[User]
/*5*/ CSVSerializer[::[Long, ::[String, ::[Boolean, HNil]]]] // failed
// diverging implicit expansion for type xyz.codefastdieyoung
// .CSVSerializer[Long :: String :: Boolean :: shapeless.HNil]
the compiler sees the same type constructor twice and the complexity of the
type parameters is increasing...
17 / 22
Lazy!
import shapeless.Lazy
implicit def HConsCSVSerializer[H, T <: HList](implicit
hSerializer: Lazy[CSVSerializer[H]],
tailSerializer: CSVSerializer[T]
): CSVSerializer[H :: T] = { t =>
s"${hSerializer(t.head)}, ${tailSerializer(t.tail)}"
}
wrap diverging implicit parameter...
it suppresses implicit divergence at compile time
it defers evaluation of the implicit parameter at runtime
17 / 22
shapeless
18 / 22
Generic programming
def fmap[A](f: A => B): F[B] = ???
18 / 22
Debugging
implicits
18 / 22
Debugging implicits
implicitly
the
reify
@showIn x
and of course - IDE short-cuts to lookup implicit values
19 / 22
MY NOSE
ITCHES
19 / 22
Issues
Compilation Times
inductive implicits
typelevel fork 2.11+
-Yinduction-heuristics
runtime overhead
...
20 / 22
Conclusion
20 / 22
Poly
ops
Lenses
TypeClass type class
Peano numbers
Utils (the, not-compile, Typable)
21 / 22
21 / 22
21 / 22
21 / 22
Thanks
@twist522
github.com/thatwist
crossroadlabs.xyz
vote.scalaua.com
22 / 22
22 / 22

Demystifying Type Class derivation with Shapeless

  • 1.
  • 2.
    // Serialize case class? CSV/JSON/XML/... caseclass User(id: Long, name: String, active: Boolean) ... def toCSV[T](t: T) = ? 2 / 22
  • 3.
    Serialize case class? CSV/JSON/XML/... caseclass User(id: Long, name: String, active: Boolean) ... def toCSV(u: User) = s"${u.id},{u.name},{u.active}" Result: scala> toCSV(User(1L, "test", true)) res1: String = 1,test,true 2 / 22
  • 4.
    Serialize case class? CSV/JSON/XML/Whatever Result: scala>toCSV(User(1L, "test", Admin(1L), Address("Wall str", 192), 1.0)) res1: String = 1, test, 1, Wall str, 192, 1.0 case class User(id: Long, name: String, role: Role, addr: Address, score: Double) sealed trait Role case class Address(street: String, house: Int) ... def toCSV(u: User) = s"${u.id}, ${u.name}, ${toCSV(u.role)}, ${toCSV(u.addr)}, ${u def toCSV(r: Role) = r match { case a: Admin => s"${a.id}, "; case c; Client => s"$ def toCSV(r: Address) = s"${a.street}, ${a.house}" 2 / 22
  • 5.
    Problem def toCSV[T](t: T):String = ??? 3 / 22
  • 6.
  • 7.
    Agenda Basics: ADTs, Products& Coproducts Type Class pattern Generic Derivation [live-code] Lazy, Aux Debugging 4 / 22
  • 8.
    How do wemodel our domain? 5 / 22
  • 9.
    case class / sealedtrait case class sealed trait case class User(id: Long, name: String, active: Boolean, role: Role) sealed trait Role case class Admin(id: Long, special: Boolean) extends Role case class Client(id: Long) extends Role How do we model our domain? 5 / 22
  • 10.
    case class / sealedtrait TupleN / Either TupleN[...] extends Product scala.Either / cats.Xor / scalaz./ type User = (Long, String, Boolean, Role) type Role = Either[Admin, Client] type Admin = (Long, Boolean) type Client = Long How do we model our domain? 5 / 22
  • 11.
    case class / sealedtrait TupleN / Either TupleN[...] extends Product scala.Either / cats.Xor / scalaz./ type User = (Long, String, Boolean, Role) type Role = Either[Admin, Client] type Admin = (Long, Boolean) type Client = Long How do we model our domain? 5 / 22
  • 12.
    case class / sealedtrait TupleN / Either HList / Coproduct HList Coproduct type User = Long :: String :: Boolean :: Role :: HNil type Role = Admin :+: Client :+: CNil type Admin = Long :: Boolean :: HNil type Client = Long :: HNil Abstracting over arity val admin: Role = Inl(2L :: true :: HNil) val sandy: User = 1 :: "Sandy" :: true :: admin :: HNil // res5: User = 1 :: Sandy :: true :: Inl(2 :: true :: HNil) 5 / 22
  • 13.
    AND types case class Tuple HList ORtypes sealed trait Either Coproduct ADT * case class User(id: Long, name: String, active: Boolean, role: Role) sealed trait Role case class Admin(id: Long, special: Boolean) extends Role case class Client(id: Long) extends Role * Stands for Algebraic Data Type 6 / 22
  • 14.
    HList val hlist =1 :: "one" :: 1L :: HNil // res0: shapeless.::[ // Int,shapeless.::[ // String,shapeless.::[Long, // shapeless.HNil // ] // ] // ] = 1 :: "one" :: 1L :: HNil hlist.head // Int hlist.tail.head // String hlist.tail.tail.head // Long val s = hlist.select[String] // returns "one". demo.select[List[Int]] // Compilation error. demo does not contain a List[Int] take, head, tail, map, flatMap, zip 7 / 22
  • 15.
    HList De nition: sealed traitHList case class ::[H, T <: HList](head : H, tail : T) extends HList // HCons case object HNil extends HList 7 / 22
  • 16.
    HList De nition: sealed traitHList case class ::[H, T <: HList](head : H, tail : T) extends HList // HCons case object HNil extends HList List De nition: sealed trait List[+A] case class ::[A](head: A, tail: List[A]) extends List[A] case object Nil extends List[Nothing] 7 / 22
  • 17.
    Coproduct Once you feelcomfortable with an HList - for Coproduct it is quite similar sealed trait Coproduct sealed trait :+:[+H, +T <: Coproduct] extends Coproduct case class Inl[+H, +T <: Coproduct](head : H) extends :+:[H, T] case class Inr[+H, +T <: Coproduct](tail : T) extends :+:[H, T] sealed trait CNil extends Coproduct Usage: // 'kind-of' Either[Int, String, Boolean] type Co = Int :+: String :+: Boolean :+: CNil val co1 = Inl(42L) // Int :+: String :+: Boolean :+: CNil val co2 = Inr(Inl("forty-two") // Int :+: String :+: Boolean :+: CNil val co3 = Inr(Inr(Inl(true)) // Int :+: String :+: Boolean :+: CNil 8 / 22
  • 18.
  • 19.
    Generic import shapeless.Generic case classUser(id: Long, name: String, active: Boolean) val generic = Generic[User] // res0: shapeless.Generic[User]{type Repr = shapeless.::[Long, // shapeless.::[String, // shapeless.::[Boolean, // shapeless.HNil // ] // ] //]} 9 / 22
  • 20.
    Generic trait Generic[A] { typeRepr def to(value: A): Repr def from(value: Repr): A } Usage: scala> val user = User(1L, "josh", true) user: User = User(1,josh,true) scala> generic.to(user) res2: res1.Repr = 1 :: josh :: true :: HNil scala> generic.from(res2) res3: User = User(1,josh,true) 9 / 22
  • 21.
    Generic trait Generic[A] { typeRepr def to(value: A): Repr def from(value: Repr): A } Usage: scala> val user = User(1L, "josh", true) user: User = User(1,josh,true) scala> generic.to(user) res2: res1.Repr = 1 :: josh :: true :: HNil scala> generic.from(res2) res3: User = User(1,josh,true) 9 / 22
  • 22.
  • 23.
    Type Class pattern scalastandard library - Numeric (Collection's sum, product, min, max) - Ordering (Collection's sorted) - CanBuildFrom (whole collections api) - IsSeqLike - IsTraversableOnce Cats, Scalaz (all of the functional abstractions like Functor, Applicative, Monad, ...) 10 / 22
  • 24.
    scala.Ordering /** * Orderingis a trait whose instances each represent * a strategy for sorting instances of a type. * ... */ trait Ordering[T] extends Comparator[T] { /** * Returns an integer whose sign communicates * how x compares to y. */ def compare(x: T, y: T): Int } Type Class pattern 11 / 22
  • 25.
    scala.Ordering De nition trait Ordering[T]extends Comparator[T] { def compare(x: T, y: T): Int } Type Class pattern 11 / 22
  • 26.
    scala.Ordering De nition Instances trait Ordering[T]extends Comparator[T] { def compare(x: T, y: T): Int } Type Class pattern object Ordering { implicit val intOrd: Ordering[Int] = new Ordering[Int] { def compare(x: Int, y: Int) = lang.Integer.compare(x, y) } implicit val longOrd: Ordering[Long] = new Ordering[Long] def compare(x: Long, y: Long) = lang.Long.compare(x, y) } ... } 11 / 22
  • 27.
    scala.Ordering De nition Instances implicit parameter trait Ordering[T]extends Comparator[T] { def compare(x: T, y: T): Int } def sorted[T](implicit ord: Ordering[T]): Repr ..or context bound def sorted[T: Ordering]: Repr Type Class pattern object Ordering { implicit val intOrd: Ordering[Int] = new Ordering[Int] { def compare(x: Int, y: Int) = lang.Integer.compare(x, y) } implicit val longOrd: Ordering[Long] = new Ordering[Long] def compare(x: Long, y: Long) = lang.Long.compare(x, y) } ... } 11 / 22
  • 28.
    scala.Ordering De nition Instances implicit parameter Usage: Type Classpattern List(1, 3, 2).sorted // at this point compiler is searching implicit value // in the ?global?, local scope, imported scope, companion o 12 / 22
  • 29.
    Type Class pattern https://github.com/mpilquist/simulacrum @typeclasstrait CSVSerializer[A] { @op("§") def serialize(a: A): String } 13 / 22
  • 30.
    Type Class pattern https://github.com/mpilquist/simulacrum @typeclasstrait CSVSerializer[A] { @op("§") def serialize(a: A): String } trait CSVSerializer[A] { def serialize(x: A, y: A): A } object CSVSerializer { def apply[A](implicit instance: CSVSerializer[A]): CSVSerializer[A] = instance trait Ops[A] { def typeClassInstance: CSVSerializer[A] def self: A def §(y: A): A = typeClassInstance.append(self, y) } trait ToCSVSerializerOps { implicit def toCSVSerializerOps[A](target: A)(implicit tc: CSVSerializer[A]): O val self = target val typeClassInstance = tc } } ... 13 / 22
  • 31.
    CSV Serializer Type Classde nition trait CSVSerializer[A] { def serialize(a: A): List[String] } 14 / 22
  • 32.
    CSV Serializer Type Classde nition trait CSVSerializer[A] { def serialize(a: A): List[String] } lets de ne some helpers.. object CSVSerializer { def apply[A](implicit serializer: CSVSerializer[A]): CSVSerializer[A] = serializer implicit class WithSerialize[A](a: A) { def toCSV(implicit serializer: CSVSerializer[A]) = serializer.serialize(a).mkString(",") } } 14 / 22
  • 33.
  • 34.
    Aux Pattern scala> traitFoo { type T } // defined trait Foo scala> def f(foo: Foo, t: foo.T) = ??? <console>:13: error: illegal dependent method type: parameter may only be referenced in a subsequent parameter section def f(foo: Foo, t: foo.T) = ??? ^ scala> 15 / 22
  • 35.
    Aux Pattern scala> traitFoo { type T } // defined trait Foo scala> def f(foo: Foo, t: foo.T) = ??? <console>:13: error: illegal dependent method type: parameter may only be referenced in a subsequent parameter section def f(foo: Foo, t: foo.T) = ??? ^ scala> Solution: scala> trait Foo { type T } // defined trait Foo scala> type Aux[T0] = Foo { type T = T0 } // defined type alias Aux scala> def f[T](foo: Aux[T], t: T) = ??? // f: [T](foo: Aux[T], t: T)Nothing 15 / 22
  • 36.
    Aux Pattern type Aux[T,Repr0] = Generic[T] { type Repr = Repr0 } 16 / 22
  • 37.
    Implicit divergence nested structure caseclass Account(id: Long, user: User) case class User(id: Long, name: String, active: Boolean) /*1*/ CSVSerializer[Account] /*2*/ CSVSerializer[::[Long, ::[User, HNil]]] /*3*/ CSVSerializer[::[User, HNil]] /*4*/ CSVSerializer[User] /*5*/ CSVSerializer[::[Long, ::[String, ::[Boolean, HNil]]]] // failed // diverging implicit expansion for type xyz.codefastdieyoung // .CSVSerializer[Long :: String :: Boolean :: shapeless.HNil] the compiler sees the same type constructor twice and the complexity of the type parameters is increasing... 17 / 22
  • 38.
    Lazy! import shapeless.Lazy implicit defHConsCSVSerializer[H, T <: HList](implicit hSerializer: Lazy[CSVSerializer[H]], tailSerializer: CSVSerializer[T] ): CSVSerializer[H :: T] = { t => s"${hSerializer(t.head)}, ${tailSerializer(t.tail)}" } wrap diverging implicit parameter... it suppresses implicit divergence at compile time it defers evaluation of the implicit parameter at runtime 17 / 22
  • 39.
  • 40.
    Generic programming def fmap[A](f:A => B): F[B] = ??? 18 / 22
  • 41.
  • 42.
    Debugging implicits implicitly the reify @showIn x andof course - IDE short-cuts to lookup implicit values 19 / 22
  • 43.
  • 44.
    Issues Compilation Times inductive implicits typelevelfork 2.11+ -Yinduction-heuristics runtime overhead ... 20 / 22
  • 45.
  • 46.
    Poly ops Lenses TypeClass type class Peanonumbers Utils (the, not-compile, Typable) 21 / 22
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.