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 ofcond
, 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
orright
constructors and a destructor calledmatch
.- The
(sumV tag v)
datatype represents a variant. Iftag
is true, then it is the left variant; otherwise it is the right variant. match e x e1 e2
works like type-case: ife
evaluates to(sumV true V)
, thene1
is called withx
bound toV
; ife
evaluates to(sumV false V)
, thene2
is called withx
bound toV
; otherwise, runtime error.
- The
(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
andrightE
with the correct type, just like we had to in the STLC with arguments for lambdas
- Answer: we can’t! We don’t know what the type of
- 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:
- See here for a complete implementation of a typechecker and interpreter for a small language with sums and products