5. Haskell offers a very expressive syntax for
defining Algebraic Data Types.
Here's how Boolean can be implemented:
data Boolean = True | False
6. True and False are data type constructors
data Boolean = True | False
This is a closed data type - once you've declared the
constructors, you no longer can add more
dynamically
8. sealed trait Boolean
case object True extends Boolean
case object False extends Boolean
*sealed closes the data type!
9. Scala vs Haskell
Simple extensibility via
inheritence - open data > Syntactic clarity
types
10. Regular algebraic data types
• Unit type
• Sum type: data Boolean = True | False
• Singleton type : data X a = X a
Combination of sum and singleton :
Either a b = Left a | Right b
• Product type: data List a = Nil|a :: List a
(combination of unit, sum and product)
• Recursive type
11. List of Integers in Haskell:
data ListI = NilI | ConsI Integer ListI
12. data ListI = NilI | ConsI Integer ListI
Usage:
let list = ConsI 3 (ConsI 2 (ConsI 1 NilI))
13. Not much more complex in Scala...
trait ListI
case object NilI extends ListI
case class ConsI(head: Int, tail: ListI)
extends ListI
14. ...especially, with some convenience methods...
trait ListI {
def ::(value: Int) = ConsI(value, this)
}
case object NilI extends ListI
case class ConsI(value: Int, list: ListI)
extends ListI
17. ...making them parametrically polymorphic
BEFORE:
trait ListI {
def ::(value: Int) = ConsI(value, this)
}
case object NilI extends ListI
case class ConsI(value: Int, list: ListI)
extends ListI
18. ...making them parametrically polymorphic
AFTER:
sealed trait ListG[+A] {
def ::[B >: A](value: B) = ConsG[B](value, this)
}
case object NilG extends ListG[Nothing]
case class ConsG[A](head: A, tail: ListG[A]) extends
ListG[A]
19. Defining a simple, product algebraic
type for binary tree is a no-brainer:
sealed trait BTreeG[+A]
case class Tip[A](value: A) extends
BTreeG[A]
case class Bin[A](left: BTreeG[A], right:
BTreeG[A]) extends BTreeG[A]
20. ListG and BTreeG are Generalized
Algebraic Data Types
And the programming approach
itself is called Generic
Programming
21. Let's say, we want to find a sum of all
the elements in the ListG and
BTreeG, now...
22. Let's say, we want to find a sum of all
the elements in the ListG and
BTreeG, now...
fold
23. def foldL[B](n: B)(f: (B, A) => B)
(list: ListG[A]): B = list match {
case NilG => n
case ConsG(head, tail) =>
f(foldL(n)(f)(tail), head)
}
}
foldL[Int, Int](0)(_ + _)(list)
24. def foldT[B](f: A => B)(g: (B, B) => B)
(tree: BTreeG[A]): B =
tree match {
case Tip(value) => f(value)
case Bin(left, right) =>
g(foldT(f)(g)(tree),foldT(f)(g)(tree))
}
foldT[Int, Int](0)(x => x)(_ + _)(tree)
30. 1. Fix type
Fix [F [_, _], A]
Higher-kinded shape Type parameter of
(pair, list, tree,...) the shape
31. Let's create an instance of Fix for
List shape
trait ListF[+A, +B]
case object NilF extends ListF[Nothing, Nothing]
case class ConsF[A, B](head: A, tail: B) extends
ListF[A, B]
type List[A] = Fix[ListF, A]
32. 2. Datatype-specific instance
of Bifunctor
trait Bifunctor[F[_, _]] {
def bimap[A, B, C, D](k: F[A, B],
f: A => C, g: B => D): F[C, D]
}
Defines mapping for the shape
33. Bifunctor instance for ListF
implicit val listFBifunctor = new Bifunctor[ListF]{
def bimap[A, B, C, D](k: ListF[A,B], f: A => C,
g: B => D): ListF[C,D] =
k match {
case NilF => NilF
case ConsF(head, tail) => ConsF(f(head), g(tail))
}
}
34. It turns out, that a wide number of
other generic operations on data types
can be expressed via bimap!
35. def map[A, B, F [_, _]](f : A => B)(t : Fix [F, A])
(implicit ft : Bifunctor [F]) : Fix [F, B]
def fold[A, B, F [_, _]](f : F[A, B] => B)(t : Fix[F,A])
(implicit ft : Bifunctor [F]) : B
def unfold [A, B, F [_, _]] (f : B => F[A, B]) (b : B)
(implicit ft : Bifunctor [F]) : Fix[F, A]
def hylo [A, B, C, F [_, _]] (f : B => F[A, B]) (g : F[A, C]
=> C)(b: B) (implicit ft : Bifunctor [F]) : C
def build [A, F [_, _]] (f : {def apply[B]: (F [A, B] => B)
=> B}): Fix[F, A]
See http://goo.gl/I4OBx
36. This approach is called
Origami patterns
• Origami patterns can be applied to generic
data types!
• Include the following GoF patterns
o Composite (algebraic data type itself)
o Iterator (map)
o Visitor (fold / hylo)
o Builder (build / unfold)
37. Those operations are called
30 loc Origami patterns
• The patterns can be applied to generic data
types!
vs 250 loc in pure Java
• Include the following GoF patterns
o Composite (algebraic data type itself)
o Iterator (map)
o Visitor (fold)
o Builder (build / unfold / hylo)