Lecture 16 — 2015-11-02

Recursive types; midterm debriefing

This lecture is written in literate Haskell; you can download the raw source.

Sum types

We first covered sum types, which are the “math” version of Haskell’s Either datatype.

t ::= ... | t1 + t2
e ::= ... | left e | right e | case e of left x -> e1 | right y -> e2

G |- e : t1
G |- left e : t1 + t2

G |- e : t2
G |- right e : t1 + t2

G |- e : t1 + t2
G,x:t1 |- e1 : t1
G,y:t2 |- e2 : t2
G |- case e of        : t
       left x -> e1
       right y -> e2

The above rules are “extrinsic” typing rules, since left and right end up guessing their arguments. We could write subscripted versions (using _), like below, to come up with intrinsic forms:

t ::= ... | t1 + t2
e ::= ... | left_t2 e | right_t1 e | ...

G |- e : t1
G |- left_t2 e : t1 + t2

G |- e : t2
G |- right_t1 e : t1 + t2

Note how the left and right constructors are annotated with the type of the other side: left_t2 says what type the programmer thinks the right side should have.

What values have type t1 + t2? Constructors make values; we’ll say that a constructor applied to a value is a value.

v ::= ... | left v | right v

---------------------------- CaseLeft
case left v of   --> e1[v/x]
  left x -> e1 
  right y -> e2

---------------------------- CaseLeft
case right v of   --> e2[v/y]
  left x -> e1 
  right y -> e2

We can also give big-step rules, using environments:

e, env VV left v    e1,env[v/x] VV v'
------------------------------------- CaseLeft
case e of       , env VV v'
  left x -> e1
  right y -> e2

e, env VV left v    e2,env[v/y] VV v'
------------------------------------- CaseRight
case e of       , env VV v'
  left x -> e1
  right y -> e2

Recursive types

Recursion in Haskell functions and Haskell datatypes uses names. Recursion in the pure lambda calculus uses the Y combinator to write “closed-form” recursive functions. We use μ types to write closed-form recursive datatypes.

Note that fold_{mu alpha. t} is meant to be interpreted as a subscript.

t ::= ... | mu alpha. t | alpha
e ::= ... | fold_{mu alpha. t} e | unfold e

G |- e : t[mu alpha. t/a]
G |- fold_{mu alpha. t} e : mu alpha. t

G |- e : mu alpha. t
G |- unfold e : t[mu alpha. t/a]

These definitions are hard to understand at first blush. Let’s look at the definition for integer lists in Haskell and work our way down:

data IntList =
  | Cons Int IntList

An IntList is either:

  • Nil, or
  • Cons n l, such that n is an Int and l is an IntList.

In algebraic types, we interpret ‘either’ using sums (written + in math and Either in Haskell). So in pseudo-Haskell, we have:

IntList = Nil + Cons Int IntList

Next, we don’t use constructor names in algebraic datatypes—we just represent the data. So Nil and Cons need to be replaced. Now, Nil has no arguments—the only information Nil provides is that it isn’t Cons. We can represent it using (), the unit type.

IntList = () + Cons Int IntList

Cons, on the other hand, has some arguments. We’ll represent them as a pair, using * (or × on paper, or (,) in Haskell).

IntList = () + (Int,IntList)

At this point, the only left to do is to get rid of the recursion. To do this, we use μ. The process is mechanical: if you have a definition of the form X = [some type t in terms of X], you translate it to X = μ α. t[α/X]. Here, that gives us:

X = mu alpha. () + (Int,alpha)

Okay: what values have μ types?

v ::= ... | fold_{mu alpha. t} v

unfold (fold_{mu alpha. t} v) --> v

We can also give rules in a big-step style:

e, env VV fold_{mu alpha. t} v
unfold e, env VV v

Hmm… still not helping. How do we build a value with folds? Let’s use the IntList above to build some lists.

IntList = mu alpha. () + (Int,alpha)

nil :: IntList
nil = fold_IntList (left ())

Okay… what about a list with elements? I’ll just write fold without its subscript, to keep things brief.

[1,2,3] = fold (right (1,fold (right (2,fold (right (3,fold (left ())))))))

Okay… how do we define cons?

cons :: Int -> IntList -> IntList
cons = \x. \xs. fold (right (x,xs))

What about head and tail?

head :: IntList -> Int
head xs =
  case unfold xs of
    left x -> diverge
    right y -> fst y

tail :: IntList -> IntList
tail xs =
  case unfold xs of
    left x -> diverge
    right y -> snd y

What about map?

map :: (Int -> Int) -> IntList -> IntList
map f xs =
  case unfold xs of
    left e -> nil
    right c -> cons (f (head c)) (map f (tail c))


Midterms were handed back at the end of class. If you don’t have yours, I’ll bring it by on Wednesday.

Please check my arithmetic and data entry on Sakai!