Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Algebraic Data Types: a very simple idea

Algebraic Data Types: a very simple idea

You have two types T and U, you can combine them in two ways: A(T,U) or B(T)|C(U). A simple but powerful idea that's surprisingly well suited for modeling real world constraints in a typesafe fashion.

Roberto Bonvallet

October 11, 2017
Tweet

More Decks by Roberto Bonvallet

Other Decks in Programming

Transcript

  1. type Bool = True | False type Option[T] = None

    | Some(T) type Result[T, E] = Success(T) | Error(E) type List[T] = EmptyList | Cons(T, List[T]) type Natural = Zero | Succesor(Natural) type BinaryTree[T] = EmptyTree | Node(BinaryTree[T], T, BinaryTree[T]) type Tree[T] = Tree(T, List[Tree[T]]) type Date = Date(Int, Int, Int) type Point2D = Cartesian(Double, Double) | Polar(Double, Double) type Suit = ♠ | ♥ | ♦ | ♣ type Rank = Ace | Two | Three | Four | Five | Six | Seven | Eight | Nine | Ten | Jack | Queen | King type PlayingCard = Joker | Card(Rank, Suit) type Shape = Circle(Point, Double) | Rectangle(Point, Point) | Polygon(List[Point]) type Expr = Plus(Expr, Expr) | Times(Expr, Expr) | LiteralNumber(Decimal) | Variable(String) type ColorName = Black | White | Red | Blue type Color = Named(ColorName) | RGB(Byte, Byte, Byte) | HSL(Degree, Percent, Percent) type TranslucidColor = TranslucidColor(Color, Percent)
  2. sealed trait Suit case object ♠ extends Suit case object

    ♥ extends Suit case object ♦ extends Suit case object ♣ extends Suit sealed trait Rank case object Ace extends Rank case object Two extends Rank case object Three extends Rank case object Four extends Rank case object Five extends Rank case object Six extends Rank case object Seven extends Rank case object Eight extends Rank case object Nine extends Rank case object Ten extends Rank case object Jack extends Rank case object Queen extends Rank case object King extends Rank sealed trait PlayingCard case object Joker extends PlayingCard final case class Card(rank: Rank, suit: Suit) extends PlayingCard type Suit = ♠ | ♥ | ♦ | ♣ type Rank = Ace | Two | Three | Four | Five | Six | Seven | Eight | Nine | Ten | Jack | Queen | King type PlayingCard = Joker | Card(Rank, Suit) I HATE Inheritance Actually this is not Scala code!
  3. // JavaScript const hand = [ { suit: 'hearts', rank:

    5 }, { suit: 'joker' }, { suit: 'diamonds', rank: 41 }, { suit: 'joker', rank: 8 }, { suit: 'puppies', rank: 'queen' } ] // Scala val hand: List[PlayingCard] = List( Card(♥, Five), Joker, Card(♦, Jack), Card(♣, Eight), Card(♠, Queen) ) type Suit = ♠ | ♥ | ♦ | ♣ type Rank = Ace | Two | Three | Four | Five | Six | Seven | Eight | Nine | Ten | Jack | Queen | King type PlayingCard = Joker | Card(Rank, Suit) "Illegal states made unrepresentable"
  4. // Pseudo Scala type Expr = Plus(Expr, Expr) | Minus(Expr,

    Expr) | Times(Expr, Expr) | Divided(Expr, Expr) | FunctionCall(String, List[Expr]) | Literal(Int) | Variable(String) // Actual Scala def eval(e: Expr, env: Map[String, Int]): Int = e match { case Plus(l, r) => eval(l) + eval(r) case Minus(l, r) => eval(l) - eval(r) case Times(Literal(0), r) => 0 case Times(l, Literal(0)) => 0 case Times(l, r) => eval(l) * eval(r) case Divided(l, r) => eval(l) / eval(r) case FunctionCall("sqrt", List(x)) => sqrt(eval(x)) case FunctionCall("abs", List(x)) => abs(eval(x)) case FunctionCall("min", xs) => xs.map(eval).reduce(min) case Literal(n) => n case Variable(v) => env(v) } Visitor
  5. // Pseudo Scala type Expr = Plus(Expr, Expr) | Minus(Expr,

    Expr) | Times(Expr, Expr) | Divided(Expr, Expr) | FunctionCall(String, List[Expr]) | Literal(Int) | Variable(String) // Actual Scala def eval(e: Expr, env: Map[String, Int]): Int = e match { case Plus(l, r) => eval(l) + eval(r) case Minus(l, r) => eval(l) - eval(r) case Times(Literal(0), r) => 0 case Times(l, Literal(0)) => 0 case Times(l, r) => eval(l) * eval(r) case Divided(l, r) => eval(l) / eval(r) case FunctionCall("sqrt", List(x)) => sqrt(eval(x)) case FunctionCall("abs", List(x)) => abs(eval(x)) case FunctionCall("min", xs) => xs.map(eval).reduce(min) case Literal(n) => n case Variable(v) => env(v) } : doesn't cover all cases : can fail
  6. // Pseudo Scala type Func = Sqrt | Abs |

    Min type Result[T, E] = Success(T) | Error(E) type EvalError = DivByZero | VarDoesntExist(String) | SqrtOfNegative | BadArity(Func, Int) type Expr = Plus(Expr, Expr) | Minus(Expr, Expr) | Times(Expr, Expr) | Divided(Expr, Expr) | FunctionCall(Func, List[Expr]) | Literal(Int) | Variable(String) // Actual Scala def eval(e: Expr, env: Map[String, Int]): Result[Int, EvalError] = e match { case Plus(l, r) => for { a <- eval(l) ; b <- eval(r) } yield a + b ... case FunctionCall(Sqrt, List(arg)) => eval(arg) match { case Success(x) if x < 0 => Error(SqrtOfNegative) case Success(x) => Success(sqrt(x)) case Error(e) => Error(e) } case FunctionCall(Sqrt, args) => Error(BadArity(Sqrt, args.length)) ... case FunctionCall(Min, Nil) => Error(BadArity(Min, 0)) case FunctionCall(Min, xs) => sequence(xs.map(eval)).map(_.reduce(min)) ... case Variable(v) => if (map contains v) Success(env(v)) else Error(VarDoesntExist(v)) } implicit object ResultIsAMonad extends Monad[Result] { … } def sequence(xs: List[Result[T, E]]): Result[List[T], E] = { … }
  7. // Pseudo Scala type Date = private Date(Int, Int, Int)

    type BadDate = BadMonth | BadDay type Result[T, E] = Success(T) | Error(E) // Actual Scala def days = List(0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) def isLeap(y: Int): Boolean = … def mkDate(y: Int, m: Int, d: Int): Result[Date, BadDate] = { if (m < 1 || m > 12) Error(BadMonth) else if (m == 2 && d == 29 && !isLeap(y)) Error(BadDay) else if (d < 1 || d > days(m)) Error(BadDay) else Success(Date(y, m, d)) } Factory
  8. The Story of the Teapot in HTML Brian Beckman &

    Erik Meijer Erik: Maybe that's why I was having so much trouble. Brian: No, no, no. You had so much trouble because you didn't have a disciplined… you had an explosion of special cases. Erik: Yes. Brian: Instead of disciplining your cases by following proper mathematical reasoning, you just started hacking it into existence."If it's like this, then if it's like that", and you had no idea whether you exhausted all the cases. And then you had at the bottom something that says "else throw exception 'this never happens'." Erik: Yes! Brian: And how often does that happen? Erik: Actually now I have to confess something. That code should never execute, but if I do it does throw an exception. So I just commented that out. But all my unit tests pass!