ADTs For The Win!
Noel Markham (47 Degrees)

Noel Markham (47 Degrees)
According to Wikipedia:
In computer programming, especially functional programming and type theory, an algebraic data type is a kind of composite type, i.e., a type formed by combining other types.
Not to be confused with abstract data types.
How many different values can these types have:
Boolean
?Integer
?Unit
?String
? List
?What about these types:
(Boolean, Int)
?Either[Boolean, Int]
?Tuples are products
Eithers are coproducts
Take a look at Jon Pretty's Talk from flatMap(Oslo) in 2014
Aside: what about function Bool to Int?
(Boolean, Int)
case class Invoice(paid: Boolean, price: Int)
Are they different?
Either[(Boolean, Int), String]
sealed trait DatabaseResponse
case class Invoice(paid: Boolean, price: Int) extends DatabaseResponse
case class ErrorMessage(description: String) extends DatabaseResponse
Is this different to the Either?
Other languages, such as Haskell, make this distinction much clearer:
data Suit = Club | Diamond | Heart | Spade
sealed abstract class Option[+A]{
//...
}
final case class Some[+A](value: A) extends Option[A]
//...
}
case object None extends Option[Nothing] {
//...
}
sealed abstract class List[+A] {
//...
}
final case class ::[B](val head: B, var tl: List[B]) extends List[B] {
//...
}
case object Nil extends List[Nothing] {
//...
}
We can encode the different types of field allowed in JSON:
sealed trait Json
case class JsonNumber(d: Double) extends Json
case class JsonBoolean(b: Boolean) extends Json
case class JsonString(s: String) extends Json
case class JsonArray(l: List[Json]) extends Json
case class JsonObject(j: Json) extends Json
case object JsonNull extends Json
Top tip: Take a look at Circe for a nice implementation
Invoice(Boolean, Int)
type by replacing the Boolean
with an ADTsealed trait PaidStatus
case object Paid extends PaidStatus
case object AwaitingPayment extends PaidStatus
DatabaseResponse => A
There are (at least) a couple of ways we can do this:
sealed trait DatabaseResponse {
def fold(fi: (Boolean, Int) => A, fe: String => A): A =
if(isSuccess) fi(this.paid, this.price) else fe(this.description)
}
sealed abstract class Option[+A] {
//...
final def fold[B](ifEmpty: => B)(f: A => B): B =
if (isEmpty) ifEmpty else f(this.get)
//...
}
def map[B](f: A => B): Option[B] = fold(None)(a => Some(f(a)))
def foreach(f: A => Unit): Unit = fold(())(a => f(a))
def get: B = fold(throw new NoSuchElementException("None.get"))(identity)
sealed trait Json {
def fold[A](
jsonNumber: Double => A,
jsonBoolean: Boolean => A,
jsonString: String => A,
jsonArray: List[Json] => A,
jsonObject: Json => A,
jsonNull: => A): A = {
//...
}
}
Imagine we add a new class to our DatabaseResponse:
case class PurchaseRequest(item: Item, cost: Int) extends DatabaseResponse
We diligently update our fold:
def fold
(fi: (Boolean, Int) => A,
fp: (Item, Int) => A,
fe: String => A): A = ...
From Wikipedia:
A red–black tree is a kind of self-balancing binary search tree. Each node of the binary tree has an extra bit, and that bit is often interpreted as the color (red or black) of the node. These color bits are used to ensure the tree remains approximately balanced during insertions and deletions.
From Purely Functional Data Structures (Chris Okasaki):
sealed trait Colour[+A]
case class R[A](a: A) extends Colour[A]
case class B[A](a: A) extends Colour[A]
sealed trait Tree[+A]
case object E extends Tree[Nothing]
case class T[A](l: Tree[A], elem: Colour[A], r: Tree[A]) extends Tree[A]
Folding the colour
sealed trait Colour[+A] {
def fold[X](fr: A => X, fb: A => X): X =
if(this.isRed) fr(this.a) else fb(this.a)
}
And add a helper to the trait:
def elem: A = fold(identity, identity)
Is a given element in the tree?
def member[A](element: A, tree: Tree[A])(implicit oa: Ordering[A]):Boolean =
tree match {
case E => false
case T(l, c, r) =>
import oa._
val e = c.elem
if (element < e) member(element, l)
else if (element > e) member(element, r)
else true
}
Inserting an element into the tree
def insert[A](element: A, tree: Tree[A])(implicit oa: Ordering[A]): Tree[A] = {
def ins(t: Tree[A]): Tree[A] =
t match {
case E => T(E, R(element), E)
case T(l, ee, r) =>
import oa._
val e = ee.elem
if (element < e) balance(ins(l), ee, r)
else if (element > e) balance(l, ee, ins(r))
else t
}
ins(tree)
}
Balancing the tree
We need to turn trees that look like this:
Balancing the tree
Into this:
def balance[A](ll: Tree[A], ee: Colour[A], rr: Tree[A]): Tree[A] =
(ll, ee, rr) match {
case (T(T(a, R(x), b), R(y), c), B(z), d) =>
T(T(a, B(x), b), R(y), T(c, B(z), d))
def balance[A](ll: Tree[A], ee: Colour[A], rr: Tree[A]): Tree[A] =
(ll, ee, rr) match {
case (T(T(a, R(x), b), R(y), c), B(z), d) =>
T(T(a, B(x), b), R(y), T(c, B(z), d))
case (T(a, R(x), T(b, R(y), c)), B(z), d) =>
T(T(a, B(x), b), R(y), T(c, B(z), d))
case (a, B(x), T(T(b, R(y), c), R(z), d)) =>
T(T(a, B(x), b), R(y), T(c, B(z), d))
case (a, B(x), T(b, R(y), T(c, R(z), d))) =>
T(T(a, B(x), b), R(y), T(c, B(z), d))
case _ => T(ll, ee, rr)
}