type classes 101 - classification beyond inheritance
Post on 16-Aug-2015
187 Views
Preview:
TRANSCRIPT
Type classes 101Classification beyond inheritance
Alexey Raga
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
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
public class Person implements ISerialisable{ public String name; public String address;
...}
public void saveToDisk(ISerialisable obj) { … }
Types classify data
Types classify data
public class Person implements ISerialisable, IJsonSerialisable, IXmlSerialisable, IPrettyPrint{ public String name; public String address;
...}
Expression problem
trait Exprcase class Lit(value: Int) extends Exprcase class Add(x: Expr, y: Expr) extends Expr
val expr = Add(Lit(15), Lit(6))
Expression problem
● Operation extensionadd new operations: eval, prettyPrint, etc.
● Data extensionadd new expressions: Mul, Pow, Neg, ec.
● Static type safetyno isInstanceOf / asInstanceOf
Expression problem
● Operation extensionadd new operations: eval, prettyPrint, etc.
● Data extensionadd new expressions: Mul, Pow, Neg, ec.
● Static type safetyno isInstanceOf / asInstanceOf
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 = ???}
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 Exprcase class Add(x: Expr, y: Expr) extends Expr
We can do better
Classification
Plants Animals
?
Classification
Fungi
Classification
Classification
Classification
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)
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))
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))
Testimony ;)
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))
Type classes in Scalatrait 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 ...}
Type classes in Scalatrait 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 = ???}
Type classes in Scalatrait 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).sumval min = List(1,2,3).min
Type classes in Scalaztrait 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}
Deriving proofs
//tuple of Equals is also an Equalimplicit 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 Semigroupimplicit 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)) }}
Expression problempackage ep
trait Exprcase class Lit(value: Int) extends Exprcase class Add[A <: Expr, B <: Expr](x: A, y: B) extends Expr
Expression problem
● Operation extensionadd new operations: eval, prettyPrint, etc.
● Data extensionadd new expressions: Mul, Pow, Neg, ec.
● Static type safetyno isInstanceOf / asInstanceOf
Expression problempackage ep.evaluateimport 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) } }}
Expression problempackage ep import evaluate._
Eval.evaluate( Add(Lit(15), Lit(6)) ) === 21
Expression problempackage ep.expressionsimport 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) } }
}
Expression problemimport evaluate._import expressions._
Eval.evaluate( Mul(Lit(2), Add(Lit(15), Lit(6))) ) === 42
Expression problempackage ep.operationsimport 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) } }
Expression problemimport expressions._import operations._import evaluate._
PPrint.prettyPrint( Mul(Lit(2), Add(Lit(15), Lit(6))) ) === "2 * (15 + 6) = 42"
Expression problem
● Operation extensionadd new operations: eval, prettyPrint, etc.
● Data extensionadd new expressions: Mul, Pow, Neg, ec.
● Static type safetyno isInstanceOf / asInstanceOf
● Add behaviours retroactivelyNo need to change existing data types
● Solution to the Expression problemOperation and data extension with static type safety
● Different kinds of operations“instance” (A => String), “factory” (String => A), etc.
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
top related