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.
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. 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
4. public class Person
implements ISerialisable
{
public String name;
public String address;
...
}
public void saveToDisk(ISerialisable obj) { … }
Types classify data
5. Types classify data
public class Person
implements ISerialisable, IJsonSerialisable, IXmlSerialisable, IPrettyPrint
{
public String name;
public String address;
...
}
6. 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))
7. 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
8. 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
9. 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 = ???
}
10. 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
19. 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))
23. 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))
24. 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
...
}
25. 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 = ???
}
26. 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
27. 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
}
28. 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))
}
}
29.
30. 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
31. 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
38. 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
39. ● 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.
40.
41.
42. 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