Slide 1

Slide 1 text

the typeclass pattern an alternative to inheritance Northeast Scala Symposium March 9, 2012 Seth Tisue [email protected] @SethTisue http://tisue.net

Slide 2

Slide 2 text

The plan 1. Concepts 2. Example 3. Pros & cons

Slide 3

Slide 3 text

Why typeclasses in Scala?

Slide 4

Slide 4 text

Why typeclasses?

Slide 5

Slide 5 text

Why? POLYMORPHISM DECOUPLING

Slide 6

Slide 6 text

Polymorphism The same operation working on different types of values. “Parametric” vs. “ad hoc” polymorphism

Slide 7

Slide 7 text

Origins pre-Haskell 1989: Philip Wadler & Stephen Blott, “How to make ad-hoc polymorphism less ad hoc” [link] (POPL ’89) 1990: Haskell

Slide 8

Slide 8 text

Typeclasses and functional programming FP = immutability functions as values no side effects typeclasses = decoupled ad hoc polymorphism

Slide 9

Slide 9 text

Haskell: Type class (language feature) Scala: Typeclass (pattern)

Slide 10

Slide 10 text

Polymorphism in OO languages – Overloading – Inheritance – Pattern matching + Traits / interfaces + Type parameters, generic types

Slide 11

Slide 11 text

Examples scala.math.Numeric scala.math.Ordering typesafe equality [link] everything else in Scalaz (monoids, monads, applicative functors...) extended example: serialization

Slide 12

Slide 12 text

Serialization example case class Person( name: String, age: Int) case class Restaurant( name: String, brunch: Boolean)

Slide 13

Slide 13 text

Serialization: Overloading def serialize(p: Person) = "Person(" + p.name + "," + p.age + ")" def serialize(r: Restaurant) = "Restaurant(" + r.name + "," + r.brunch + ")" Uh oh...

Slide 14

Slide 14 text

Serialization: Inheritance abstract class Serializable { def serialize: String } class Person(...) extends Serializable { override def serialize = ... } class Restaurant(...) extends Serializable { override def serialize = ... } Uh oh...

Slide 15

Slide 15 text

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?

Slide 16

Slide 16 text

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 { ...

Slide 17

Slide 17 text

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 ...

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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 ...

Slide 20

Slide 20 text

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")

Slide 21

Slide 21 text

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))

Slide 22

Slide 22 text

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)

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

What have we got? Type safety Extensibility (add serializable types) Extensibility (add new kinds of serialization) Decoupling

Slide 25

Slide 25 text

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)

Slide 26

Slide 26 text

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))

Slide 27

Slide 27 text

Typeclasses are static Object oriented dispatch happens at runtime Pattern matching happens at runtime Typeclasses work at compile time only

Slide 28

Slide 28 text

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]]

Slide 29

Slide 29 text

Interaction with inheritance [link 1] (Stack Overflow) [link 2] (Stack Overflow)

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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]

Slide 32

Slide 32 text

Further reading Scala in Depth, section 7.3 Josh Suereth (book, Manning, 2012) [info/preorder] “Typeclasses in Scala” Erik Osheim (PHASE talk, 2011) [slides]

Slide 33

Slide 33 text

thank you for listening ask something Seth Tisue [email protected] @SethTisue http://tisue.net