39
ORIGAMI patterns with Algebraic Data Types @remeniuk

Algebraic Data Types and Origami Patterns

Embed Size (px)

Citation preview

Page 1: Algebraic Data Types and Origami Patterns

ORIGAMI patterns with Algebraic Data Types

@remeniuk

Page 2: Algebraic Data Types and Origami Patterns

What is “algebra”?

1. A set of elements 2.Some operations that map

elements to elements

Page 3: Algebraic Data Types and Origami Patterns

List

1. Elements: “list” and “list element”

2.Operations: Nil and ::(cons)

Page 4: Algebraic Data Types and Origami Patterns

In programming languages, algebraic data types are defined with the set of constructors that wrap other types

Page 5: Algebraic Data Types and Origami Patterns

Haskell offers a very expressive syntax for defining Algebraic Data Types.

data Boolean = True | False

Here's how Boolean can be implemented:

Page 6: Algebraic Data Types and Origami Patterns

data Boolean = True | False

This is a closed data type - once you've declared the constructors, you no longer can add more dynamically

True and False are data type constructors

Page 7: Algebraic Data Types and Origami Patterns

In Scala, algebraic data type declaration

is a bit more verbose...

Page 8: Algebraic Data Types and Origami Patterns

sealed trait Boolean case object True extends Boolean case object False extends Boolean

*sealed closes the data type!

Page 9: Algebraic Data Types and Origami Patterns

Scala vs Haskell

Simple extensibility via inheritence - open data types

Syntactic clarity >

Page 10: Algebraic Data Types and Origami Patterns

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

Page 11: Algebraic Data Types and Origami Patterns

data ListI = NilI | ConsI Integer ListI

List of Integers in Haskell:

Page 12: Algebraic Data Types and Origami Patterns

data ListI = NilI | ConsI Integer ListI

Usage:

let list = ConsI 3 (ConsI 2 (ConsI 1 NilI))

Page 13: Algebraic Data Types and Origami Patterns

Not much more complex in Scala...

trait ListI case object NilI extends ListI case class ConsI(head: Int, tail: ListI) extends ListI

Page 14: Algebraic Data Types and Origami Patterns

...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

Page 15: Algebraic Data Types and Origami Patterns

...and here we are:

val list = 3 :: 2 :: 1 :: NilI

Page 16: Algebraic Data Types and Origami Patterns

Lets make our data types

more useful

Page 17: Algebraic Data Types and Origami Patterns

...making them parametrically polymorphic

trait ListI { def ::(value: Int) = ConsI(value, this) } case object NilI extends ListI case class ConsI(value: Int, list: ListI) extends ListI

BEFORE:

Page 18: Algebraic Data Types and Origami Patterns

...making them parametrically polymorphic

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]

AFTER:

Page 19: Algebraic Data Types and Origami Patterns

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]

Page 20: Algebraic Data Types and Origami Patterns

ListG and BTreeG are Generalized Algebraic Data Types

And the programming approach itself is called Generic

Programming

Page 21: Algebraic Data Types and Origami Patterns

Let's say, we want to find a sum of all the elements in the ListG and BTreeG, now...

Page 22: Algebraic Data Types and Origami Patterns

Let's say, we want to find a sum of all the elements in the ListG and BTreeG, now...

fold

Page 23: Algebraic Data Types and Origami Patterns

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)

Page 24: Algebraic Data Types and Origami Patterns

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)

Page 25: Algebraic Data Types and Origami Patterns

Obviously, foldL and foldT have very much in common.

Page 26: Algebraic Data Types and Origami Patterns

Obviously, foldL and foldT have very much in common.

In fact, the biggest difference is in

the shape of the data

Page 27: Algebraic Data Types and Origami Patterns

That would be great, if we could abstract away from data type...

Page 28: Algebraic Data Types and Origami Patterns

That would be great, if we could abstract away from data type...

With Datatype Generic programming we can!

Page 29: Algebraic Data Types and Origami Patterns

Requirements:

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

Bifunctor

Page 30: Algebraic Data Types and Origami Patterns

1. Fix type

Fix [F [_, _], A]

Higher-kinded shape (pair, list, tree,...)

Type parameter of the shape

Page 31: Algebraic Data Types and Origami Patterns

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]

Page 32: Algebraic Data Types and Origami Patterns

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

Page 33: Algebraic Data Types and Origami Patterns

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)) } }

Page 34: Algebraic Data Types and Origami Patterns

It turns out, that a wide number of other generic operations on data types can be expressed via bimap!

Page 35: Algebraic Data Types and Origami Patterns

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

Page 36: Algebraic Data Types and Origami Patterns

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)

Page 37: Algebraic Data Types and Origami Patterns

Those operations are called

Origami patterns • The 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) o Builder (build / unfold / hylo)

30 loc

vs 250 loc in pure Java

Page 38: Algebraic Data Types and Origami Patterns

Live demo! http://goo.gl/ysv5Y

Page 39: Algebraic Data Types and Origami Patterns

Thanks for watching!