# Recursive types

Can we express `nat` in terms of sums and products?

``````nat = [z: unit;  s: ?]
``````

well, the `s` alternative carries a natural, so:

``````nat = [z: unit;  s: nat]
``````

This is a recursive type equation. In general, we have data structures with a regular structure that may grow to arbitrary size. Naturals, lists, trees, abstract syntax, derivations, etc. However, recursive types are hardly limited to standard conceptions of data structures. Once function or lazy types are involved, the range of expressible structure becomes far wider than what is possible with eager sums and products alone.

### Type Isomorphisms

Directly equating `nat` and `[z: unit; s: nat]` can complicate both the metatheory and implementation of recursive types. So, instead of talking about equality, we will talk about isomorphism.

We can say two types are isomorphic if we have two functions

``````f: T1 -> T2
g: T2 -> T1
``````

that are mutually inverses (that is, both for all `x:T2`, `f(g x) = x` and for all `y:T1`, `g (f y) = y`. That is, `f o g` is the identity on `T2` and `g o f` is the identity function on `T1`.)

We introduce a new type operator `mu` to define recursive types that will satisfy equations of the type above up to isomorphism. For `nat`, we have:

``````nat = mu(t.[z: unit;  s: t])
``````

which means, “`nat` is the (infinite) type satisfying the isomorphism `t ~= [z: unit; s: t]`.” Generally,

``````mu(t.T) ~= [mu(t.T)/t]T.
``````

That is, the recursive type `mu(t.T)` is the solution the above isomorphism.

### Examples

`nat` is one of the simplest and most basic recursive types. Here are a few more examples.

List of natural numbers (with `nil` and `cons` alternatives):

``````nat_list = mu(t.[n: unit; c: (nat * nat_list)])
``````

Binary trees (with `leaf` and `branch` alternatives):

``````tree = mu(t.[l: unit; b: (nat * tree * tree)])
``````

## Details

### “Free” isomorphism

To witness the isomorphism induced by mu, we will introduce two corresponding constructs: `fold(e)` and `unfold(e)`, which convert a term of recursive type back and forth between these two isomorphic types.

``````T ::= t | mu(t.T)
e ::= fold[t.T](e) | unfold(e)
``````

### Statics (rules 16.2a-b)

``````                  G |- e : [mu(t.T)/t]T
-------------------------------
G |- fold[t.T](e) : mu(t.T)

G |- e : mu(t.T)
--------------------------------
G | unfold(e) : [mu(t.T)/t]T
``````

### Dynamics (rules 16.3a-d)

``````                            e val
------------------
fold[t.T](e) val

e -> e'
---------------------------------
fold[t.T](e) -> fold[t.T](e')

e -> e'
-----------------------
unfold(e) -> unfold(e')

fold[t.T](e) val
---------------------------
unfold(fold[t.T](e)) -> e
``````

Key is last rule: elimination is inverse of introduction.

Type safety – standard.

### Examples revisted:

Nats:

``````z = fold(z.<>)
s(e) = fold(s.e)
ifz(e; e0; x.e1) => case unfold(e) {z._ -> e0 | s.x -> e1}
``````

where underscore means “ignore”.

List of nats:

``````nil = fold(n.<>)
cons(e1;e2) = fold(c.<e1,e2>)

isnil = \(l:nat_list) case unfold(e) {n._ -> true | c._ -> false}
hd = \(l:nat_list) case unfold(l) {n._ -> z | c.x -> x.1}
tl = \(l:nat_list) case unfold(l) {n._ -> l | c.x -> x.2}
``````

Note: we arbitrarily choose values for the error cases of `hd` and `tl` because we do not have exceptions.

The “hungry” function:

``````hungry = fix[H](f.fold(\(x:nat) f))
H = ?
unfold(hungry)(3) = ?
unfold(unfold(hungry)(3))(9) = ?
``````

``````H = mu(t.nat -> t)
unfold(hungry)(3) = hungry
unfold(unfold(hungry)(3))(9) = hungry
``````

### Recursive values from recursive types.

Aside: equi-recursive types

For brevity, we will now treat isomorphism implicitly, leaving out the explicit folding and unfolding. Here is a well-typed fixed-point combinator:

``````fix_T : (T -> T) -> T
fix_T = \(f : T -> T) (\(x : mu(t.t -> T)) f (x(x))) (\(x : mu(t.t -> T)) f (x(x)))
``````

So, recursive types are enough to allow general recursion. Key point is that we can view the variable `x` in the self-application `(x x)` at two different (but isomorphic) types: `mu(t.t -> T) -> T` and `mu(t.t -> T)`.

However, there’s more: we can type the “untyped” lambda calculus with recursive types, from which the typability of the Y-combinator comes for free.

As Bob showed early on, the lambda calculus is built on this recursive type equation:

``````D ~= D -> D
``````

As we’ve seen, `D` can be solved with a recursive type:

``````D = mu t.t -> t.
``````

So, the lambda calculus has but one type: it is “uni-typed” rather than “untyped”. We can translate terms from our previous syntax to a typed syntax as follows:

``````       x# = x
(\(x) e)# = fold(\(x:D) e#
(e1 e2)# = unfold(e1#) (e2#)
``````

As with HW1’s SKI tasks, you can check that the translation is sound and complete. Put another way, that beta-reduction commutes with the translation. However, you’ll notice that the typed version is rather heavy-weight because of the need to deal with all of the folding and unfolding. Of particular interest is the case of applicatoin wherein the `unfold` implicitly checks that `e1` is in fact a function.

Which leads us to “dynamic typing”…

Edit