ORIGAMI
patterns with
Algebraic Data Types
           @remeniuk
What is “algebra”?
1. A set of elements
2.Some operations that map
   elements to elements
List
1. Elements: “list” and “list
   element”
2.Operations: Nil and ::(cons)
In programming languages,
algebraic data types are defined
with the set of constructors that
wrap other types
Haskell offers a very expressive syntax for
     defining Algebraic Data Types.



Here's how Boolean can be implemented:

data Boolean = True | False
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
In Scala, algebraic data type
         declaration
   is a bit more verbose...
sealed trait Boolean
case object True extends Boolean
case object False extends Boolean

     *sealed closes the data type!
Scala                  vs   Haskell
Simple extensibility via
inheritence - open data    >    Syntactic clarity

types
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
List of Integers in Haskell:

data ListI = NilI | ConsI Integer ListI
data ListI = NilI | ConsI Integer ListI


                  Usage:
let list = ConsI 3 (ConsI 2 (ConsI 1 NilI))
Not much more complex in Scala...


trait ListI
case object NilI extends ListI
case class ConsI(head: Int, tail: ListI)
extends ListI
...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
...and here we are:

val list = 3 :: 2 :: 1 :: NilI
Lets make our data types
   more useful
...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
...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]
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]
ListG and BTreeG are Generalized
Algebraic Data Types

 And the programming approach
     itself is called Generic
         Programming
Let's say, we want to find a sum of all
the elements in the ListG and
BTreeG, now...
Let's say, we want to find a sum of all
the elements in the ListG and
BTreeG, now...


               fold
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)
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)
Obviously, foldL and foldT have
very much in common.
Obviously, foldL and foldT have
very much in common.

In fact, the biggest difference is in
the shape of the data
That would be great, if we could
abstract away from data type...
That would be great, if we could
abstract away from data type...

   With Datatype Generic
    programming we can!
Requirements:

• Fix data type (recursive data type)
• Datatype-specific instance of
  Bifunctor
1. Fix type

         Fix [F [_, _], A]


Higher-kinded shape      Type parameter of
(pair, list, tree,...)   the shape
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]
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
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))
    }

}
It turns out, that a wide number of
other generic operations on data types
can be expressed via bimap!
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
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)
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)
Live demo!
 http://goo.gl/ysv5Y
Thanks for watching!

Algebraic Data Types and Origami Patterns

  • 1.
  • 2.
    What is “algebra”? 1.A set of elements 2.Some operations that map elements to elements
  • 3.
    List 1. Elements: “list”and “list element” 2.Operations: Nil and ::(cons)
  • 4.
    In programming languages, algebraicdata types are defined with the set of constructors that wrap other types
  • 5.
    Haskell offers avery expressive syntax for defining Algebraic Data Types. Here's how Boolean can be implemented: data Boolean = True | False
  • 6.
    True and Falseare 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
  • 7.
    In Scala, algebraicdata type declaration is a bit more verbose...
  • 8.
    sealed trait Boolean caseobject 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 datatypes • 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 Integersin 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 morecomplex in Scala... trait ListI case object NilI extends ListI case class ConsI(head: Int, tail: ListI) extends ListI
  • 14.
    ...especially, with someconvenience methods... trait ListI { def ::(value: Int) = ConsI(value, this) } case object NilI extends ListI case class ConsI(value: Int, list: ListI) extends ListI
  • 15.
    ...and here weare: val list = 3 :: 2 :: 1 :: NilI
  • 16.
    Lets make ourdata types more useful
  • 17.
    ...making them parametricallypolymorphic 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 parametricallypolymorphic 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 BTreeGare Generalized Algebraic Data Types And the programming approach itself is called Generic Programming
  • 21.
    Let's say, wewant to find a sum of all the elements in the ListG and BTreeG, now...
  • 22.
    Let's say, wewant 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)
  • 25.
    Obviously, foldL andfoldT have very much in common.
  • 26.
    Obviously, foldL andfoldT have very much in common. In fact, the biggest difference is in the shape of the data
  • 27.
    That would begreat, if we could abstract away from data type...
  • 28.
    That would begreat, if we could abstract away from data type... With Datatype Generic programming we can!
  • 29.
    Requirements: • Fix datatype (recursive data type) • Datatype-specific instance of Bifunctor
  • 30.
    1. Fix type Fix [F [_, _], A] Higher-kinded shape Type parameter of (pair, list, tree,...) the shape
  • 31.
    Let's create aninstance 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 forListF 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 iscalled 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 arecalled 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.
  • 39.