We already know that without recursion life can be very boring… So we obviously want to be able to have recursive functions — but the question is how will they interact with our type system. One thing that we have seen is that by just having functions we get recursion. This was achieved by the Y combinator function. It seems like the same should apply to our simple typed language. The core of the Y combinator was using an expression similar to Omega that generates the infinite loop that is needed. In our language:

{call {fun {x} {call x x}} {fun {x} {call x x}}}

This expression was impossible to evaluate completely since it never
terminates, but it served as a basis for the Y combinator so we need to
be able to perform this kind of infinite loop. Now, consider the type
of the first `x`

— it’s used in a `call`

expression as a function, so
its type must be a function type, say τ₁->τ₂. In addition, its argument
is `x`

itself so its type is also τ₁ — this means that we have:

τ₁ -> τ₂ = τ₁

and from this we get:

=> τ₁ = τ₁ -> τ₂

= (τ₁ -> τ₂) -> τ₂

= ((τ₁ -> τ₂) -> τ₂) -> τ₂

= ...

= (τ₁ -> τ₂) -> τ₂

= ((τ₁ -> τ₂) -> τ₂) -> τ₂

= ...

And this is a type that does not exist in our type system, since we can only have finite types. Therefore, we have a proof by contradiction that this expression cannot be typed in our system.

This is closely related to the fact that the typed language we have
described so far is “strongly normalizing”: no matter what program you
write, it will always terminate! To see this, very informally, consider
this language without functions — this is clearly a language where all
programs terminate, since the only way to create a loop is through
function applications. Now add functions and function application —
in the typing rules for the resulting language, each `fun`

creates a
function type (creates an arrow), and each function application consumes
a function type (deletes one arrow) — since types are finite, the
number of arrows is finite, which means that the number of possible
applications is finite, so all programs must run in finite time.

Note that when we discussed how to type the Y combinator we needed to use a

`Rec`

constructor — something that the current type system has. Using that, we could have easily solve the`τ₁ = τ₁ -> τ₂`

equation with`(Rec τ₁ (τ₁ -> τ₂))`

.

In the our language, therefore, the halting problem doesn’t even exist, since all programs (that are properly typed) are guaranteed to halt. This property is useful in many real-life situations (consider firewall rules, configuration files, devices with embedded code). But the language that we get is very limited as a result — we really want the power to shoot our feet…

As we have seen, our language is strongly normalizing, which means that
to get general recursion, we must introduce a new construct (unlike
previously, when we didn’t really need one). We can do this as we
previously did — by adding a new construct to the language, or we can
somehow extend the (sub) language of type descriptions to allow a new
kind of type that can be used to solve the `τ₁ = τ₁ -> τ₂`

equation. An
example of this solution would be similar to the `Rec`

type constructor
in Typed Racket: a new type constructor that allows a type to refer to
itself — and using `(Rec τ₁ (τ₁ -> τ₂))`

as the solution. However,
this complicates things: type descriptions are no longer unique, since
we have `Num`

, `(Rec this Num)`

, and `(Rec this (Rec that Num))`

that
are all equal.

For simplicity we will now take the first route and add `rec`

— an
explicit recursive binder form to the language (as with `with`

, we’re
going back to `rec`

rather than `bindrec`

to keep things simple).

First, the new BNF:

<PICKY> ::= <num>

| <id>

| { + <PICKY> <PICKY> }

| { < <PICKY> <PICKY> }

| { fun { <id> : <TYPE> } : <TYPE> <PICKY> }

| { call <PICKY> <PICKY> }

| { with { <id> : <TYPE> <PICKY> } <PICKY> }

| { rec { <id> : <TYPE> <PICKY> } <PICKY> }

| { if <PICKY> <PICKY> <PICKY> }

<TYPE> ::= Number

| Boolean

| ( <TYPE> -> <TYPE> )

| <id>

| { + <PICKY> <PICKY> }

| { < <PICKY> <PICKY> }

| { fun { <id> : <TYPE> } : <TYPE> <PICKY> }

| { call <PICKY> <PICKY> }

| { with { <id> : <TYPE> <PICKY> } <PICKY> }

| { rec { <id> : <TYPE> <PICKY> } <PICKY> }

| { if <PICKY> <PICKY> <PICKY> }

<TYPE> ::= Number

| Boolean

| ( <TYPE> -> <TYPE> )

We now need to add a typing judgment for `rec`

expressions. What should
it look like?

???

———————————————————————————

Γ ⊢ {rec {x : τ₁ V} E} : τ₂

———————————————————————————

Γ ⊢ {rec {x : τ₁ V} E} : τ₂

`rec`

is similar to all the other local binding forms, like `with`

, it
can be seen as a combination of a function and an application. So we
need to check the two things that those rules checked — first, check
that the body expression has the right type assuming that the type
annotation given to `x`

is valid:

Γ[x:=τ₁] ⊢ E : τ₂ ???

———————————————————————————

Γ ⊢ {rec {x : τ₁ V} E} : τ₂

———————————————————————————

Γ ⊢ {rec {x : τ₁ V} E} : τ₂

Now, we also want to add the other side — making sure that the τ₁ type annotation is valid:

Γ[x:=τ₁] ⊢ E : τ₂ Γ ⊢ V : τ₁

——————————————————————————————

Γ ⊢ {rec {x : τ₁ V} E} : τ₂

——————————————————————————————

Γ ⊢ {rec {x : τ₁ V} E} : τ₂

But that will not be possible in general — `V`

is an expression that
can include `x`

itself — that’s the whole point. The conclusion is
that we should use a similar trick to the one that we used to specify
evaluation of recursive binders — the same environment is used for
both the named expression and for the body expression:

Γ[x:=τ₁] ⊢ E : τ₂ Γ[x:=τ₁] ⊢ V : τ₁

—————————————————————————————————————

Γ ⊢ {rec {x : τ₁ V} E} : τ₂

—————————————————————————————————————

Γ ⊢ {rec {x : τ₁ V} E} : τ₂

You can also see now that this rule adds an arrow type to the Γ type environment, in a way that makes it possible to use it over and over, making it possible to run infinite loops in this language.

Our complete language specification is below.

<PICKY> ::= <num>

| <id>

| { + <PICKY> <PICKY> }

| { < <PICKY> <PICKY> }

| { fun { <id> : <TYPE> } : <TYPE> <PICKY> }

| { call <PICKY> <PICKY> }

| { with { <id> : <TYPE> <PICKY> } <PICKY> }

| { rec { <id> : <TYPE> <PICKY> } <PICKY> }

| { if <PICKY> <PICKY> <PICKY> }

<TYPE> ::= Number

| Boolean

| ( <TYPE> -> <TYPE> )

Γ ⊢ n : Number

Γ ⊢ x : Γ(x)

Γ ⊢ A : Number Γ ⊢ B : Number

———————————————————————————————

Γ ⊢ {+ A B} : Number

Γ ⊢ A : Number Γ ⊢ B : Number

———————————————————————————————

Γ ⊢ {< A B} : Boolean

Γ[x:=τ₁] ⊢ E : τ₂

——————————————————————————————————————

Γ ⊢ {fun {x : τ₁} : τ₂ E} : (τ₁ -> τ₂)

Γ ⊢ F : (τ₁ -> τ₂) Γ ⊢ V : τ₁

——————————————————————————————

Γ ⊢ {call F V} : τ₂

Γ ⊢ C : Boolean Γ ⊢ T : τ Γ ⊢ E : τ

———————————————————————————————————————

Γ ⊢ {if C T E} : τ

Γ ⊢ V : τ₁ Γ[x:=τ₁] ⊢ E : τ₂

——————————————————————————————

Γ ⊢ {with {x : τ₁ V} E} : τ₂

Γ[x:=τ₁] ⊢ V : τ₁ Γ[x:=τ₁] ⊢ E : τ₂

—————————————————————————————————————

Γ ⊢ {rec {x : τ₁ V} E} : τ₂

| <id>

| { + <PICKY> <PICKY> }

| { < <PICKY> <PICKY> }

| { fun { <id> : <TYPE> } : <TYPE> <PICKY> }

| { call <PICKY> <PICKY> }

| { with { <id> : <TYPE> <PICKY> } <PICKY> }

| { rec { <id> : <TYPE> <PICKY> } <PICKY> }

| { if <PICKY> <PICKY> <PICKY> }

<TYPE> ::= Number

| Boolean

| ( <TYPE> -> <TYPE> )

Γ ⊢ n : Number

Γ ⊢ x : Γ(x)

Γ ⊢ A : Number Γ ⊢ B : Number

———————————————————————————————

Γ ⊢ {+ A B} : Number

Γ ⊢ A : Number Γ ⊢ B : Number

———————————————————————————————

Γ ⊢ {< A B} : Boolean

Γ[x:=τ₁] ⊢ E : τ₂

——————————————————————————————————————

Γ ⊢ {fun {x : τ₁} : τ₂ E} : (τ₁ -> τ₂)

Γ ⊢ F : (τ₁ -> τ₂) Γ ⊢ V : τ₁

——————————————————————————————

Γ ⊢ {call F V} : τ₂

Γ ⊢ C : Boolean Γ ⊢ T : τ Γ ⊢ E : τ

———————————————————————————————————————

Γ ⊢ {if C T E} : τ

Γ ⊢ V : τ₁ Γ[x:=τ₁] ⊢ E : τ₂

——————————————————————————————

Γ ⊢ {with {x : τ₁ V} E} : τ₂

Γ[x:=τ₁] ⊢ V : τ₁ Γ[x:=τ₁] ⊢ E : τ₂

—————————————————————————————————————

Γ ⊢ {rec {x : τ₁ V} E} : τ₂

An important concept that we have avoided so far is user-defined types. This issue exists in practically all languages, including the ones we did so far, since a language without the ability to create new user-defined types is a language with a major problem. (As a side note, we did talk about mimicking an object system using plain closures, but it turns out that this is insufficient as a replacement for true user-defined types — you can kind of see that in the Schlac language, where the lack of all types mean that there is no type error.)

In the context of a statically typed language, this issue is even more
important. Specifically, we talked about typing recursive code, but we
should also consider typing recursive data. For example, we will start
with a `length`

function in an extension of the language that has
`empty?`

, `rest`

, and `NumCons`

and `NumEmpty`

constructors:

{rec {length : ???

{fun {l : ???} : Number

{if {empty? l}

0

{+ 1 {call length {rest l}}}}}}

{call length {NumCons 1 {NumCons 2 {NumCons 3 {NumEmpty}}}}}}

{fun {l : ???} : Number

{if {empty? l}

0

{+ 1 {call length {rest l}}}}}}

{call length {NumCons 1 {NumCons 2 {NumCons 3 {NumEmpty}}}}}}

But adding all of these new functions as built-ins is getting messy: we
want our language to have a form for defining new kinds of data. In
this example — we want to be able to define the `NumList`

type for
lists of numbers. We therefore extend the language with a new
`with-type`

form for creating new user-defined types, using variants in
a similar way to our own course language:

{with-type {NumList [NumEmpty]

[NumCons Number ???]}

{rec {length : ???

{fun {l : ???} : Number

...}}

...}}

[NumCons Number ???]}

{rec {length : ???

{fun {l : ???} : Number

...}}

...}}

We assume here that the `NumList`

definition provides us with a number
of new built-ins — `NumEmpty`

and `NumCons`

constructors, and assume
also a `cases`

form that can be used to both test a value and access its
components (with the constructors serving as patterns). This makes the
code a little different than what we started with:

{with-type {NumList [NumEmpty]

[NumCons Number ???]}

{rec {length : ???

{fun {l : ???} : Number

{cases l

[{NumEmpty} 0]

[{NumCons x r} {+ 1 {call length r}}]}}}

{call length {NumCons 1 {NumCons 2 {NumCons 3 {NumEmpty}}}}}}}

[NumCons Number ???]}

{rec {length : ???

{fun {l : ???} : Number

{cases l

[{NumEmpty} 0]

[{NumCons x r} {+ 1 {call length r}}]}}}

{call length {NumCons 1 {NumCons 2 {NumCons 3 {NumEmpty}}}}}}}

The question is what should the `???`

be filled with? Clearly,
recursive data types are very common and we need to support them. The
scope of `with-type`

should therefore be similar to `rec`

, except that
it works at the type level: the new type is available for its own
definition. This is the complete code now:

{with-type {NumList [NumEmpty]

[NumCons Number NumList]}

{rec {length : (NumList -> Number)

{fun {l : NumList} : Number

{cases l

[{NumEmpty} 0]

[{NumCons x r} {+ 1 {call length r}}]}}}

{call length {NumCons 1 {NumCons 2 {NumCons 3 {NumEmpty}}}}}}}

[NumCons Number NumList]}

{rec {length : (NumList -> Number)

{fun {l : NumList} : Number

{cases l

[{NumEmpty} 0]

[{NumCons x r} {+ 1 {call length r}}]}}}

{call length {NumCons 1 {NumCons 2 {NumCons 3 {NumEmpty}}}}}}}

(Note that in the course language we can do just that, and in addition,
the `Rec`

type constructor can be used to make up recursive types.)

An important property that we would like this type to have is for it to
be *well founded*: that we’d never get stuck in some kind of type-level
infinite loop. To see that this holds in this example, note that some
of the variants are self-referential (only `NumCons`

here), but there is
at least one that is not (`NumEmpty`

) — if there wasn’t any simple
variant, then we would have no way to construct instances of this type
to begin with!

[As a side note, if the language has lazy semantics, we could use such types — for example:

{with-type {NumList [NumCons Number NumList]}

{rec {ones : NumList {NumCons 1 ones}}

...}}

{rec {ones : NumList {NumCons 1 ones}}

...}}

Reasoning about such programs requires more than just induction though.]