Skip to content
Snippets Groups Projects
recursive-types.org 5.06 KiB

Inductive, Coinductive and Recursive Types in GTT

In order to apply the GTT model to non-trivial (statically typed) programs, we need to add some facility for inductive, co-recursive and/or recursive types.

In principle this should not be a problem: the uniqueness principles are derivable in the presence of functoriality of connectives, and recursive types don’t disrupt that. One difficulty is in establishing formally that the casts we derive for recursive types are actually values for recursive value types and stacks for recursive stack types. The usual presentation of recursive types by just adding in an isomorphism between (rec X. A) and A[rec X.A] is not sufficient because we would use something like the Y combinator to implement functorial stuff. This presentation is useful for minimalistic operational semantics, but is insufficient for a minimalistic axiomatic semantics.

By contrast, Inductive and Co-inductive types present no difficulty at all. We add in an injection and the induction principle, with the induction principle being a value form. This makes it easy to show that the upcast implementation is a value. The same holds in the dual: the coinduction principle for co-inductive computations should be astack.

Then the result should hold because any recursive type can be written using inductive types and guarded recursive types where guarded here means the variable occurs under a thunk.

That is any recursive value type `rec X. A` can be written in the form `rec X. μ X’. [A]` where every occurrence of `X’` in `A` is strictly positive and every occurrence of `X` in `A` occurs under a thunk `U`. For example, take a simple Schemish CBV dynamic type:

rec D. 1 + (D * D) + U(D -> F D)

The intuition about this type is that dynamically typed values are S-Expressions whose leaves are either nil or a unary procedure. The factorization makes this clearer:

rec D. μ T. 1 + (T * T) + U(D -> F D)

We can think of `D` as the full recursive dynamic type and `T` as representing the inductive first-order component of the type: S-expressions.

I haven’t worked out the details but I think this observation should be sufficient to prove syntactically that upcasts are values/downcasts are stacks.

Precision and Equi-Recursion vs Iso-Recursion

Static typing naturally lends itself to iso-recursion whereas dynamic typing naturally lends itself to equi-recursion. Gradual typing can accommodate this difference quite easily: in the cast calculus, the recursion is iso-recursive, but the precision is equi-recursive.

But what rules should we have? Should we just state that they are equi-dynamic with their one level unrolling?

rec X. A == A[rec X.A]

Or should we say that in fact they are least pre-fixed points in the precision ordering? I haven’t thought through the consequences yet but my gut says least fixed point will be more useful.

The lfp rules are simple. We say first that the type mu X. A is a pre-fixed point of X.A:


A[mu X. A/X] <= mu X. A

and second that it is the least such pre-fixed point:

A[A’/X] <= A’


mu X. A <= A’

From this we can derive that lfp are functorial:

X <= X’ |- A <= A’


mu X. A <= mu X’. A’

by using the lfp property and monotonicity of all connectives:

A[mu X’. A’/X] <= A’[mu X’. A’/X’] <= mu X’. A’


A[mu X’. A’/X] <= mu X’. A’


mu X. A <= mu X’. A’

Recursive Types as a Precision-Least Pre-fixed Point

Here’s an interesting idea, somewhat inspired by denotational semantics. What if the recursive type mu X. A were just defined to be the least pre-fixed point of X.A in the dynamism ordering? That is, we treat mu X. A similar to the dynamic type ? in that we only interact with it via casts.

Then we could define for example:

G |- V : A[mu X. A/X]


G |- roll(V) : mu X. A =: <mu X. A <–< A[mu X. A/X]>V

and

G |- V : mu X. A


G |- unroll(V) : A[mu X. A/X] =: <A[mu X.A/X] <–< mu X.A>V

where the dynamism derivation of the latter comes from

A <= A and A[mu X. A/X] <= mu X.A ——————————-subst A[A[mu X.A/X]/X] <= A[mu X.A /X] ———————-least pre-fixed point mu X. A <= A[mu X.A/X]

we can prove that roll, unroll form an isomorphism from our proof that equi-dynamic types are isomorphic.

What’s even cooler than that is we can derive reduction rules:

Say we have a derivation A[A’/X] <= A’ ————-lfp mu X. A <= A’

Then there is an upcast

<A’ <–< mu X. A>V

We can derive a reduction rule for this cast when it is applied to roll:

<A’ <–< mu X.A>roll(V) = <A’ <–< mu X.A><mu X. A <–< A[mu X.A/X]>V = <A’ <–< A[A’/X]><A[A’/X] <–< A[mu X.A/X]>V

By coherence of upcasts, we need only verify that appropriate type dynamism derivations can be constructed:

  1. A[A’/X] <= A’
  2. A <= A and mu X.A <= A’ —————————–subst A[mu X.A/X] <= A[A’/X]

TODO: look at the downcasts involving mu, and dualize everything for computation types.