Lecture 13: Sum types and dynamic safety

  • Logistics:
    • Quiz tomorrow, out at 9am, due 11:59pm friday
    • Quiz will have the following objective style guide for code: Your solution should make use of type-case instead of cond, should contain type annotations for all functions, should have a comment describing the purpose of each function, and should have at least 2 test cases for each defined function. We will deduct 1 point from your style score for each missing component.
    • Steven OH shifted tomorrow: now at 1:30 – 2:30PM
    • Homework due tonight
  • Goals for today:
    • See another example of designing a type-system for sum types and variants.
    • See an example of an abstract machine and understand dynamic safety

Sum types and variants

  • Another core feature of Plait we’ve been using extensively is algebraic datatypes (ADTs) or variants: datatypes that are one of several possible values
  • In plait, we construct and destruct ADTs using variant constructors and type-case:
(define-type LeftOrRight
  [left (l : Number)]
  [right (r : Symbol)])

(type-case LeftOrRight (left 10)
  [(left n) n]
  [(right r) (error 'what? "")])
  • Our abstract syntax trees have all been ADTs
  • Let’s make a tiny syntax for a language with ADTs that can only have left or right constructors and a destructor called match.
    • The (sumV tag v) datatype represents a variant. If tag is true, then it is the left variant; otherwise it is the right variant.
    • match e x e1 e2 works like type-case: if e evaluates to (sumV true V), then e1 is called with x bound to V; if e evaluates to (sumV false V), then e2 is called with x bound to V; otherwise, runtime error.
(define-type Exp
  [let1E (x : Symbol) (e1 : Exp) (e2 : Exp)]
  [varE (x : Symbol)]
  [numE (n : Number)]
  [leftE (e : Exp)]
  [addE (e1 : Exp) (e2 : Exp)]
  [rightE (e : Exp)]
  [matchE (x : Symbol) (e : Exp) (left : Exp) (right : Exp)])

(define-type Value
  [numV (n : Number)]
  ; tag is #t if left, #f if right
  [sumV (tag : Boolean) (v : Value)])

(define-type-alias Env (Hashof Symbol Value))
(define mt-env (hash empty)) ;; "empty environment"

(define (lookup (n : Env) (s : Symbol))
  (type-case (Optionof Value) (hash-ref n s)
    [(none) (error 'runtime "not bound")]
    [(some v) v]))

(extend : (Env Symbol Value -> Env))
(define (extend old-env new-name value)
  (hash-set old-env new-name value))

(interp : (Env Exp -> Value))
(define (interp env e)
  (type-case Exp e
    [(let1E x e1 e2)
     (interp (extend env x (interp env e1)) e2)]
    [(varE s) (lookup env s)]
    [(numE n) (numV n)]
    [(leftE e) (sumV #t (interp env e))]
    [(addE e1 e2) (numV (+ (numV-n (interp env e1)) (numV-n (interp env e2))))]
    [(rightE e) (sumV #f (interp env e))]
    [(matchE x e1 leftE rightE)
     (type-case Value (interp env e1)
       [(sumV tag v)
        (if tag
            (interp (extend env x v) leftE)
            (interp (extend env x v) rightE))]
       [else (error 'runtime "invalid")])]))

Making a typechecker for sum types

  • How can we cause a runtime error for our interpreter?
; match on something that is not an ADT
(interp mt-env (matchE 'x (numE 10) (varE 'x) (numE 30)))
(runtime error)

; treat 'x as the wrong type in (matchE 'x e e2 e2)
> (interp mt-env (matchE 'x (leftE (leftE (numE 10))) (addE (varE 'x) (numE 10)) (numE 5)))
(contract violation)
  • What are the possible types?
    • Three two of values: numbers and a sum value that one of two possible values
  • So, we use the following type:
(define-type Type
  [TNum]
  [TSum (t1 : Type) (t2 : Type)])
  • Question: how do we know what type (leftE (numE 10)) is?
    • Answer: we can’t! We don’t know what the type of rightE would be
    • Solution: annotate leftE and rightE with the correct type, just like we had to in the STLC with arguments for lambdas
  • New grammar:
(define-type Exp
  [let1E (x : Symbol) (e1 : Exp) (e2 : Exp)]
  [varE (x : Symbol)]
  [numE (n : Number)]
  [leftE (t : Type) (e : Exp)]
  [rightE (t : Type) (e : Exp)]
  [matchE (x : Symbol) (e : Exp) (left : Exp) (right : Exp)])
  • Typing rules:
\[\dfrac{\texttt{Γ ⊢ e : T1}}{\texttt{Γ ⊢ (leftE (T1 + T2) e) : T1 + T2}}~\text{(T-Left)} \quad\quad \dfrac{\texttt{Γ ⊢ e : T2}}{\texttt{Γ ⊢ (rightE (T1 + T2) e) : T1 + T2}}~\text{(T-Right)}\] \[\dfrac{\texttt{Γ ⊢ e : T1 + T2} \quad\quad \texttt{Γ ∪ \{x↦T1\} ⊢ e1 : T} \quad\quad \texttt{Γ ∪ \{x↦T2\} ⊢ e2 : T}} {\texttt{Γ ⊢ match x e e1 e2 : T}}\]
  • See here for a complete implementation of a typechecker and interpreter for a small language with sums and products