Algebraic Data Types and Origami Patterns

4,824 views
4,658 views

Published on

0 Comments
2 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
4,824
On SlideShare
0
From Embeds
0
Number of Embeds
2,303
Actions
Shares
0
Downloads
55
Comments
0
Likes
2
Embeds 0
No embeds

No notes for slide

Algebraic Data Types and Origami Patterns

  1. 1. ORIGAMIpatterns withAlgebraic Data Types @remeniuk
  2. 2. What is “algebra”?1. A set of elements2.Some operations that map elements to elements
  3. 3. List1. Elements: “list” and “list element”2.Operations: Nil and ::(cons)
  4. 4. In programming languages,algebraic data types are definedwith the set of constructors thatwrap other types
  5. 5. Haskell offers a very expressive syntax for defining Algebraic Data Types.Heres how Boolean can be implemented:data Boolean = True | False
  6. 6. True and False are data type constructors data Boolean = True | FalseThis is a closed data type - once youve declared theconstructors, you no longer can add moredynamically
  7. 7. In Scala, algebraic data type declaration is a bit more verbose...
  8. 8. sealed trait Booleancase object True extends Booleancase object False extends Boolean *sealed closes the data type!
  9. 9. Scala vs HaskellSimple extensibility viainheritence - open data > Syntactic claritytypes
  10. 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. 11. List of Integers in Haskell:data ListI = NilI | ConsI Integer ListI
  12. 12. data ListI = NilI | ConsI Integer ListI Usage:let list = ConsI 3 (ConsI 2 (ConsI 1 NilI))
  13. 13. Not much more complex in Scala...trait ListIcase object NilI extends ListIcase class ConsI(head: Int, tail: ListI)extends ListI
  14. 14. ...especially, with some convenience methods...trait ListI { def ::(value: Int) = ConsI(value, this)}case object NilI extends ListIcase class ConsI(value: Int, list: ListI)extends ListI
  15. 15. ...and here we are:val list = 3 :: 2 :: 1 :: NilI
  16. 16. Lets make our data types more useful
  17. 17. ...making them parametrically polymorphic BEFORE:trait ListI { def ::(value: Int) = ConsI(value, this)}case object NilI extends ListIcase class ConsI(value: Int, list: ListI)extends ListI
  18. 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]) extendsListG[A]
  19. 19. Defining a simple, product algebraictype for binary tree is a no-brainer:sealed trait BTreeG[+A]case class Tip[A](value: A) extendsBTreeG[A]case class Bin[A](left: BTreeG[A], right:BTreeG[A]) extends BTreeG[A]
  20. 20. ListG and BTreeG are GeneralizedAlgebraic Data Types And the programming approach itself is called Generic Programming
  21. 21. Lets say, we want to find a sum of allthe elements in the ListG andBTreeG, now...
  22. 22. Lets say, we want to find a sum of allthe elements in the ListG andBTreeG, now... fold
  23. 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. 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)
  25. 25. Obviously, foldL and foldT havevery much in common.
  26. 26. Obviously, foldL and foldT havevery much in common.In fact, the biggest difference is inthe shape of the data
  27. 27. That would be great, if we couldabstract away from data type...
  28. 28. That would be great, if we couldabstract away from data type... With Datatype Generic programming we can!
  29. 29. Requirements:• Fix data type (recursive data type)• Datatype-specific instance of Bifunctor
  30. 30. 1. Fix type Fix [F [_, _], A]Higher-kinded shape Type parameter of(pair, list, tree,...) the shape
  31. 31. Lets create an instance of Fix forList shapetrait ListF[+A, +B]case object NilF extends ListF[Nothing, Nothing]case class ConsF[A, B](head: A, tail: B) extendsListF[A, B] type List[A] = Fix[ListF, A]
  32. 32. 2. Datatype-specific instance of Bifunctortrait 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. 33. Bifunctor instance for ListFimplicit 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. 34. It turns out, that a wide number ofother generic operations on data typescan be expressed via bimap!
  35. 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. 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. 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)
  38. 38. Live demo! http://goo.gl/ysv5Y
  39. 39. Thanks for watching!

×