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

Type classes 101

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.

Type classes 101

Inheritance and interfaces implementation are often used in languages like Java in order to express "Is-a" and "Can-do" capabilities. In Scala we can do better by separating these concerns using the concept of type classes.

Code: http://goo.gl/DU3oSR

Avatar for Alexey Raga

Alexey Raga

March 11, 2015
Tweet

Other Decks in Programming

Transcript

  1. String Int List[Boolean] case class Name(value: String) case class Age(value:

    Int) case class Person(name: Name, age: Age) case class Gang(leader: Person, members: List[Person]) Types classify data
  2. String Int List[Boolean] case class Name(value: String) case class Age(value:

    Int) case class Person(name: Name, age: Age) case class Gang(leader: Person, members: List[Person]) Types classify data
  3. public class Person implements ISerialisable { public String name; public

    String address; ... } public void saveToDisk(ISerialisable obj) { … } Types classify data
  4. Expression problem trait Expr case class Lit(value: Int) extends Expr

    case class Add(x: Expr, y: Expr) extends Expr val expr = Add(Lit(15), Lit(6))
  5. Expression problem • Operation extension add new operations: eval, prettyPrint,

    etc. • Data extension add new expressions: Mul, Pow, Neg, ec. • Static type safety no isInstanceOf / asInstanceOf
  6. Expression problem • Operation extension add new operations: eval, prettyPrint,

    etc. • Data extension add new expressions: Mul, Pow, Neg, ec. • Static type safety no isInstanceOf / asInstanceOf
  7. Expression problem trait Expr { def eval: Int def print:

    String } case class Lit(value: Int) extends Expr { def eval = ??? def print = ??? } case class Add(x: Expr, y: Expr) extends Expr { def eval = ??? def print = ??? }
  8. Expression problem trait Expr { def eval: Int = this

    match { case Lit => ??? case Add => ??? } def print: String = this match { case Lit => ??? case Add => ??? } } case class Lit(value: Int) extends Expr case class Add(x: Expr, y: Expr) extends Expr
  9. Classifying types trait Serialisable[A] { def serialise(obj: A) : Array[Byte]

    } object PersonSerialisable extends Serialisable[Person] { def serialise(obj: Person): Array[Byte] = ??? } def saveToDisk[A](obj: A, ser: Serialisable[A]) = { val data = ser.serialise(obj) ??? } saveToDisk(Person("john", 99), PersonSerialisable)
  10. Type classes classify types trait Serialisable[A] { def serialise(obj: A)

    : Array[Byte] } implicit object PersonSerialisable extends Serialisable[Person] { def serialise(obj: Person): Array[Byte] = ??? } def saveToDisk[A](obj: A)(implicit ser: Serialisable[A]) = { val data = ser.serialise(obj) ??? } saveToDisk(Person("john", 99))
  11. Type classes classify types // already defined in Scala //

    def implicitly[T](implicit e: T) = e def saveToDisk[A: Serialisable](obj: A) = { val ser = implicitly[Serialisable[A]] val data = ser.serialise(obj) ... } saveToDisk(Person("john", 99))
  12. Just saying... import serialisation.json._ //import serialisation.csv._ //import serialisation.xml._ def saveToDisk[A](obj:

    A)(implicit ser: Serialisable[A]) = { val data = ser.serialise(obj) ??? } saveToDisk(Person("john", 99))
  13. Type classes in Scala trait Ordering[T] { def compare(x :

    T, y : T) : Int def lteq(x : T, y : T) : Boolean = compare(x, y) <= 0 def gteq(x : T, y : T) : Boolean = compare(x, y) => 0 ... } trait Numeric[T] extends Ordering[T] { def plus(x : T, y : T) : T def minus(x : T, y : T) : T def negate(x : T) : T ... }
  14. Type classes in Scala trait Ordering[T] { def compare(x :

    T, y : T) : Int def lteq(x : T, y : T) : Boolean = compare(x, y) <= 0 def gteq(x : T, y : T) : Boolean = compare(x, y) => 0 } trait Numeric[T] extends Ordering[T] { def plus(x : T, y : T) : T def minus(x : T, y : T) : T def negate(x : T) : T } trait TraversableOnce[+A] { def sum[B >: A](implicit num : scala.Numeric[B]) : B = ??? def min[B >: A](implicit cmp : scala.Ordering[B]) : A = ??? def max[B >: A](implicit cmp : scala.Ordering[B]) : A = ??? }
  15. Type classes in Scala trait Ordering[T] { def compare(x :

    T, y : T) : Int def lteq(x : T, y : T) : Boolean = compare(x, y) <= 0 def gteq(x : T, y : T) : Boolean = compare(x, y) => 0 } trait Numeric[T] extends Ordering[T] { def plus(x : T, y : T) : T def minus(x : T, y : T) : T def negate(x : T) : T } trait TraversableOnce[+A] { def sum[B >: A](implicit num : scala.Numeric[B]) : B = ??? def min[B >: A](implicit cmp : scala.Ordering[B]) : A = ??? def max[B >: A](implicit cmp : scala.Ordering[B]) : A = ??? } val sum = List(1,2,3).sum val min = List(1,2,3).min
  16. Type classes in Scalaz trait Equal[A] { def equal(a1 :

    A, a2 : A) : Boolean } trait Show[A] { def shows(a : A) : String } trait Functor[F[_]] { def map[A, B](fa: F[A])(f: A => B): F[B] } trait Semigroup[A] { def append(a1 : A, a2 : => A) : A } trait Monoid[A] extends Semigroup[A] { def zero : A }
  17. Deriving proofs //tuple of Equals is also an Equal implicit

    def tuple2Equal[A: Equal, B: Equal]: Equal[(A, B)] = new Equal[(A, B)] { def equal(a1: (A, B), a2: (A, B)) : Boolean = a1._1 === a2._1 && a1._2 === a2._2 } //tuple of Semigroups is also a Semigroup implicit def tuple2Semigroup[A: Semigroup, B: Semigroup]: Semigroup[(A, B)] = { new Semigroup[(A, B)] { def append(p1: (A, B), p2: => (A, B)) = ((p1._1 |+| p2._1), (p1._2 |+| p2._2)) } }
  18. Expression problem package ep trait Expr case class Lit(value: Int)

    extends Expr case class Add[A <: Expr, B <: Expr](x: A, y: B) extends Expr
  19. Expression problem • Operation extension add new operations: eval, prettyPrint,

    etc. • Data extension add new expressions: Mul, Pow, Neg, ec. • Static type safety no isInstanceOf / asInstanceOf
  20. Expression problem package ep.evaluate import ep._ trait Eval[A <: Expr]

    { def eval(expr: A) : Int } object Eval { def evaluate[A <: Expr](expr: A)(implicit evA: Eval[A]) = evA.eval(expr) implicit object LitEval extends Eval[Lit] { def eval(expr: Lit) = expr.value } implicit def addEval[A <: Expr, B <: Expr](implicit evA: Eval[A], evB: Eval[B]) = { new Eval[Add[A, B]] { def eval(expr: Add[A, B]) = evA.eval(expr.x) + evB.eval(expr.y) } } }
  21. Expression problem package ep.expressions import ep._ import evaluate._ case class

    Mul[A <: Expr, B <: Expr](x: A, y: B) extends Expr object Mul { implicit def mulEval[A <: Expr, B <: Expr](implicit evA: Eval[A], evB: Eval[B]) = { new Eval[Mul[A, B]] { def eval(expr: Mul[A, B]) = evA.eval(expr.x) * evB.eval(expr.y) } } }
  22. Expression problem package ep.operations import ep._ import ep.expressions._ trait PPrint[A

    <: Expr] { def print(expr: A) : String } object PPrint { def prettyPrint[A <: Expr](expr: A)(implicit pa: PPrint[A]) = pa.print(expr) implicit object LitPrint extends PPrint[Lit] { def print(expr: Lit) = expr.value.toString } implicit def mulPrint[A <: Expr, B <: Expr](implicit pA: PPrint[A], pB: PPrint[B]) = { new PPrint[Mul[A, B]] { def print(expr: Mul[A, B]) = pA.print(expr.x) + " * " + pB.print(expr.y) } }
  23. Expression problem • Operation extension add new operations: eval, prettyPrint,

    etc. • Data extension add new expressions: Mul, Pow, Neg, ec. • Static type safety no isInstanceOf / asInstanceOf
  24. • Add behaviours retroactively No need to change existing data

    types • Solution to the Expression problem Operation and data extension with static type safety • Different kinds of operations “instance” (A => String), “factory” (String => A), etc.
  25. What about us? Isn't it enough? No we're not in

    paradise This is who we are This is what we've got No it's not our paradise