Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

Type classes 101

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