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 =
Nil
| Cons Int IntList
An IntList
is either:
Nil
, orCons n l
, such thatn
is anInt
andl
is anIntList
.
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
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!