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

NE Scala 2012: The Typeclass Pattern an Alternative to Inheritance

marakana
March 26, 2012

NE Scala 2012: The Typeclass Pattern an Alternative to Inheritance

Seth Tisue delivers a beginner-friendly introduction to the Typeclass pattern in Scala in this video from the 2012 Northeast Scala Symposium. Video for this presentation available here: http://mrkn.co/wlefn

marakana

March 26, 2012
Tweet

More Decks by marakana

Other Decks in Programming

Transcript

  1. Polymorphism The same operation working on different types of values.

    “Parametric” vs. “ad hoc” polymorphism
  2. Origins pre-Haskell 1989: Philip Wadler & Stephen Blott, “How to

    make ad-hoc polymorphism less ad hoc” [link] (POPL ’89) 1990: Haskell
  3. Typeclasses and functional programming FP = immutability functions as values

    no side effects typeclasses = decoupled ad hoc polymorphism
  4. Polymorphism in OO languages – Overloading – Inheritance – Pattern

    matching + Traits / interfaces + Type parameters, generic types
  5. Examples scala.math.Numeric scala.math.Ordering typesafe equality [link] everything else in Scalaz

    (monoids, monads, applicative functors...) extended example: serialization
  6. Serialization example case class Person( name: String, age: Int) case

    class Restaurant( name: String, brunch: Boolean)
  7. Serialization: Overloading def serialize(p: Person) = "Person(" + p.name +

    "," + p.age + ")" def serialize(r: Restaurant) = "Restaurant(" + r.name + "," + r.brunch + ")" Uh oh...
  8. Serialization: Inheritance abstract class Serializable { def serialize: String }

    class Person(...) extends Serializable { override def serialize = ... } class Restaurant(...) extends Serializable { override def serialize = ... } Uh oh...
  9. Serialization: trait (interface) trait Serializable { def serialize: String }

    class Person(...) extends Serializable with ... { override def serialize = ... } class Restaurant(...) extends Serializable with ... { override def serialize = ... } Uh oh?
  10. Serialization: pattern matching aka “typecasing” def serialize(x: Any) = x

    match { case p: Person => "Person(" + p.name + ... case r: Restaurant => "Restaurant(" + r.name + ... ... } } def serializeToJSON(x: Any) = x match { ...
  11. Serialization: Typeclass (almost) trait Serializable[T] { def ser(t: T): String

    } def serialize[T](t: T, s: Serializable[T]) = s.ser(t) object PersonIsSerializable extends Serializable[Person] { def ser(p: Person) = "Person(" + p.name + ... } object RestaurantIsSerializable ...
  12. So far so good scala> serialize(Person("Seth", 40), PersonIsSerializable) res7: String

    = Person(Seth,40) scala> serialize(Restaurant(...), RestaurantIsSerializable) res8: String = Restaurant(...) but it’s not automatic yet
  13. Serialization: Typeclass trait Serializable[T] { def ser(t: T): String }

    def serialize[T](t: T) (implicit s: Serializable[T]) = s.ser(t) implicit object PersonIsSerializable extends Serializable[Person] { def ser(p: Person) = "Person(" + p.name + ... } implicit object RestaurantIsSerializable ...
  14. It works! scala> serialize(Person("Seth", 40)) res7: String = Person(Seth,40) and

    it's type-safe: scala> serialize("Seth") [...] error: could not find implicit value for parameter s: Serializable[java.lang.String] serialize("Seth")
  15. Syntax: “context bound” let's define something that works across the

    whole typeclass: implicit def ListIsSerializable[T] (implicit ev: Serializable[T]) = ... can be shortened to: implicit def ListIsSerializable[T : Serializable] = new Serializable[List[T]] { def ser(xs: List[T]) = xs.map(serialize(_)) .mkString("List(", ",", ")") } scala> serialize(List(Person("Seth", 40), Person("Kaarin", 43))) res1: String = List(Person(Seth,40),Person(Kaarin,43))
  16. Syntax: OO style so far, function style: scala> serialize(Person("Seth", 40))

    res7: String = Person(Seth,40) but suppose I'd rather write: scala> Person("Seth", 40).serialize res8: String = Person(Seth,40)
  17. Syntax: OO style trait Serializable[T] { def ser(t: T): String

    } implicit def addSerialize[T](t: T) (implicit s: Serializable[T]) = new { def serialize = s.ser(t) } scala> Person("Seth", 40).serialize res8: String = Person(Seth,40) implicit parameters and implicit conversion
  18. What have we got? Type safety Extensibility (add serializable types)

    Extensibility (add new kinds of serialization) Decoupling
  19. Advantage: Scala “Unlike with [Haskell’s] type classes, the scope of

    an implicit parameter can be controlled, and competing implicit parameters can coexist in different parts of one program.” Odersky et al, “An Overview of the Scala Programming Language” (2004)
  20. Example: disambiguating scala> implicit val ByName: Ordering[Person] = Ordering.by(_.name) ByName:

    Ordering[Person] = ... scala> people.sorted res3: List[Person] = List(Person(Kaarin,43), Person(Seth,40)) scala> implicit val ByAge: Ordering[Person] = Ordering.by(_.age) ByAge: Ordering[Person] = ... scala> people.sorted error: ambiguous implicit values: both value ByName of type => Ordering[Person] and value ByAge of type => Ordering[Person] match expected type scala.math.Ordering[Person] scala> people.sorted(ByName) res5: List[Person] = List(Person(Kaarin,43), Person(Seth,40)) scala> people.sorted(ByAge) res6: List[Person] = List(Person(Seth,40), Person(Kaarin,43))
  21. Typeclasses are static Object oriented dispatch happens at runtime Pattern

    matching happens at runtime Typeclasses work at compile time only
  22. This is not dynamic dispatch! This works: scala> serialize(List(Person("Seth", 40),

    Person("Kaarin", 43))) res1: String = List(Person(Seth,40),Person(Kaarin,43)) But this doesn't: scala> serialize(List(Person("Seth", 40), Restaurant("Haveli", true))) error: could not find implicit value for parameter s: Serializable[List[Product with Serializable]]
  23. Other downsides Pattern boilerplate Not obvious “this is a typeclass”

    Implicit parameters complicate error messages, complicate use of higher order functions, etc. Less discoverable in Scaladoc, IDE, etc Performance overhead
  24. Language proposals performance: Josh Suereth (& Jorge Ortiz, David Hall):

    SIP-13: Implicit classes [link] Martin Odersky, Jeff Olson, Paul Phillips, Josh Suereth: SIP-15: Value classes [link] syntax: “Note: [SIP-13] does not include any syntax sugar fixes for defining typeclasses. I have an alternative, much more controversial, SIP that outlines an annotation-based approach to generating typeclass boilerplate. I can send it to those who are interested, because it’s designed to be achieved via annotations + a compiler plugin currently.” — Josh Suereth, scala-lang group [link]
  25. Further reading Scala in Depth, section 7.3 Josh Suereth (book,

    Manning, 2012) [info/preorder] “Typeclasses in Scala” Erik Osheim (PHASE talk, 2011) [slides]