Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Combining Scalaz and Shapeless for Great Good

Lars Hupel
October 25, 2013

Combining Scalaz and Shapeless for Great Good

Scalaz is an extension to the Scala standard library providing purely functional data structures and useful abstractions. Shapeless is a library for generic programming and pushes Scala’s type system quite far. Recently, there have been some developments in providing interoperability between both libraries, which turns out to be massively useful.

In this talk, I showcased some of the cool available techniques, including abstracting over heterogeneous data and the arity of functions, using macros to generate type class instances, and (de)serializing data with zero boilerplate.

Lars Hupel

October 25, 2013
Tweet

More Decks by Lars Hupel

Other Decks in Programming

Transcript

  1. Type Classes in Scala Bad News Scala offers no native

    support for type classes Good News ... but they can be encoded 3
  2. Type Classes in Scala Example: Ordering trait Ordering[A] { def

    compare(x: A, y: A): Order } ▶ type classes are (first-class) traits 4
  3. Type Classes in Scala Example: Ordering trait Ordering[A] { def

    compare(x: A, y: A): Order } ▶ type classes are (first-class) traits ▶ can use arbitrary language features 4
  4. Type Classes in Scala Example: Ordering trait Ordering[A] { def

    compare(x: A, y: A): Order def equal(x: A, y: A) = compare(x, y) == Order.EQ } ▶ type classes are (first-class) traits ▶ can use arbitrary language features e.g. default implementations 4
  5. Type Classes in Scala Example: Ordering implicit object IntHasOrdering extends

    Ordering[Int] { def compare(x: Int, y: Int) = if (x < y) Order.LT else if (x > y) Order.GT else Order.EQ } ▶ instances are (first-class) implicits 5
  6. Type Classes in Scala Example: Ordering implicit object IntHasOrdering extends

    Ordering[Int] { def compare(x: Int, y: Int) = if (x < y) Order.LT else if (x > y) Order.GT else Order.EQ } ▶ instances are (first-class) implicits ▶ can use arbitrary language features 5
  7. Type Classes in Scala Example: Ordering implicit object IntHasOrdering extends

    Ordering[Int] { def compare(x: Int, y: Int) = if (x < y) Order.LT else if (x > y) Order.GT else Order.EQ } ▶ instances are (first-class) implicits ▶ can use arbitrary language features e.g. macros 5
  8. Implementing Instances class Vector2D(val x: Int, val y: Int) class

    Vector3D(val x: Int, val y: Int, val z: Int) ▶ we want: default instances for ... ▶ Semigroup (pointwise addition) ▶ Ordering (lexicographic order) ▶ and more? 6
  9. Implementing Instances class Vector2D(val x: Int, val y: Int) class

    Vector3D(val x: Int, val y: Int, val z: Int) ▶ we want: default instances for ... ▶ Semigroup (pointwise addition) ▶ Ordering (lexicographic order) ▶ and more? ▶ very repetitive to implement by hand ▶ need a mechanism to automate that task 6
  10. Implementing Instances class Vector2D(val x: Int, val y: Int) class

    Vector3D(val x: Int, val y: Int, val z: Int) ▶ Scala already provides some automation 6
  11. Implementing Instances case class Vector2D(x: Int, y: Int) case class

    Vector3D(x: Int, y: Int, z: Int) ▶ Scala already provides some automation 6
  12. Implementing Instances case class Vector2D(x: Int, y: Int) case class

    Vector3D(x: Int, y: Int, z: Int) ▶ Scala already provides some automation ▶ generates: ▶ toString ▶ equals ▶ hashCode 6
  13. Implementing Instances case class Vector2D(x: Int, y: Int) case class

    Vector3D(x: Int, y: Int, z: Int) ▶ Scala already provides some automation ▶ generates: ▶ toString ▶ equals ▶ hashCode ▶ Problem: baked into the compiler 6
  14. Implementing Instances case class Vector2D(x: Int, y: Int) case class

    Vector3D(x: Int, y: Int, z: Int) ▶ Scala already provides some automation ▶ generates: ▶ toString ▶ equals ▶ hashCode ▶ Problem: baked into the compiler 6
  15. Goals ... for library designers 1. Define a type class.

    2. Implement some base instances. 3. Declare how to combine instances. 7
  16. Goals ... for library designers 1. Define a type class.

    2. Implement some base instances. 3. Declare how to combine instances. ... for library users 1. Define some types. 7
  17. The Essence of Data Types ▶ we are dealing with

    algebraic data types ▶ ... consisting of one or more cases ▶ ... consisting of zero or more fields 8
  18. The Essence of Data Types Example: Lists sealed trait List

    case class Cons(head: Int, tail: List) extends List case class Nil() extends List 9
  19. The Essence of Data Types Example: Lists sealed trait List

    case class Cons(head: Int, tail: List) extends List case class Nil() extends List Cons(1, Cons(2, Nil())) . . Cons . 1 . Cons . 2 . Nil 9
  20. Abstract Representation Examples Data type case class Vector3D(x: Int, y:

    Int, z: Int) Representation type Repr = (Int, Int, Int) 11
  21. Abstract Representation Examples Data type sealed trait Shape case class

    Circle(radius: Int) extends Shape case class Rectangle(height: Int, width: Int) extends Shape Representation type Repr = Either[Int, (Int, Int)] 11
  22. Type Classes as Type Constructors Realization: type classes are regular

    types ... Scala’s encoding makes abstraction possible 12
  23. Composing Instances Example: Ordering Given val A: Ordering[A] val B:

    Ordering[B] ... we can implement val prod: Ordering[(A, B)] val sum: Ordering[Either[A, B]] 13
  24. Composing Instances Example: Ordering Given val A: Ordering[A] val B:

    Ordering[B] ... we can implement val prod: Ordering[(A, B)] val sum: Ordering[Either[A, B]] Question: Can we do the same for Semigroup? 13
  25. Putting it together Macros! Input Data type type T Type

    class type C[_] Output Instance val instance: C[T] 15
  26. Putting it together Macros! Input Data type type T Type

    class type C[_] Output Instance val instance: C[T] 15
  27. Problems ▶ base instances are resolved via regular implicit search

    ▶ What happens for (mutually) recursive data types? 16
  28. Problems ▶ base instances are resolved via regular implicit search

    ▶ What happens for (mutually) recursive data types? ▶ solution: make macro aware of recursion (aka tying the knot) ▶ self recursion: easy, use this instead of implicitly ▶ mutual recursion: require user setup ▶ indirect recursion: ongoing ... 16
  29. Use Case: Serialization type ByteString = // ... def encode(a:

    A): ByteString def decode(bs: ByteString): Option[(A, ByteString)] 20
  30. Use Case: Serialization ... for library designers 1. Define a

    type class. ✓ 2. Implement some base instances. ✓ 3. Declare how to combine instances. ✓ 21
  31. Recap: Representation Data type case class Employee(name: String, years: Int,

    salary: Float) Representation type Repr = (String, Int, Float) 24
  32. Recap: Representation Data type case class Employee(name: String, years: Int,

    salary: Float) Representation type Repr = String :: Int :: Float :: HNil 24
  33. Use Case: Applicative Composition The Problem ▶ You have Options

    of different types. ▶ You want to apply a function which takes plain values. 26
  34. Use Case: Applicative Composition The Problem ▶ You have Options

    of different types. ▶ You want to apply a function which takes plain values. The Naïve Solution Pattern Matching 26
  35. Use Case: Applicative Composition The Problem ▶ You have Options

    of different types. ▶ You want to apply a function which takes plain values. The Scalaz Solution (nameOpt |@| yearOpt |@| salaryOpt) { Employee(_, _, _) } 26
  36. Use Case: Applicative Composition The Problem ▶ You have Options

    of different types. ▶ You want to apply a function which takes plain values. The Scalaz Solution (nameOpt |@| yearOpt |@| salaryOpt) { Employee(_, _, _) } Downside: only goes up to 14 26
  37. Use Case: Applicative Composition The Problem ▶ You have Options

    of different types. ▶ You want to apply a function which takes plain values. The Unified Solution val makeEmployee = // ... sequence(nameOpt :: yearOpt :: salaryOpt :: HNil). map(makeEmployee) 26
  38. Use Case: Applicative Composition The Problem ▶ You have Options

    of different types. ▶ You want to apply a function which takes plain values. The Unified Solution val makeEmployee = // ... sequence(nameOpt :: yearOpt :: salaryOpt :: HNil). map(makeEmployee) 26