# Divergence, or Non-Termination

Most dependently typed languages are not Turing complete. This is because, as explained earlier, it is crucial to the soundness of a type theory to have all functions terminate. This means that you cannot program, say, an interpreter for a general-purpose programming language in a language like Coq, since such an interpreter would not be able to handle programs that intentionally loop forever. 1

F*’s logical core of total (and ghost) functions can only express
terminating computations. However, F*’s also allows expressing
non-terminating or *divergent* computations, relying on the effect
system to isolate divergent computations from the logical core. In
particular, the computation type `Dv t`

describes a computation that
may loop forever, but if it completes, it returns a value of type
`t`

.

Relying on the effect system as a dependency tracking mechanism, F*
ensures that `Tot`

computations cannot rely on `Dv`

computations
by placing `Dv`

above `Tot`

in the effect hierarchy, while,
conversely, a total computation `Tot t`

can be silently promoted to
`Dv t`

, the type of computations that may not terminate, i.e., ```
Tot
< Dv
```

in the effect partial order.

Recursive functions that return computations in the `Dv`

effect are
not checked for termination. As such, using the `Dv`

effect, one
can write programs such as the one below, which computes Collatz
sequences—whether or not
this program terminates for all inputs is an open problem.

```
(* You can program a function to compute Collatz sequences
... though no one knows if it actually terminates for all n *)
let rec collatz (n:pos)
: Dv (list pos)
= if n = 1 then [n]
else if n % 2 = 0
then n::collatz (n / 2)
else n::collatz (3 * n + 1)
```

In this chapter, we’ll look in detail at the `Dv`

effect and how it
interacts with other features of the language, including the other
effects, recursive type definitions, and the styles of programming and
proving it enables.

- 1
In place of general recursion and potential non-termination, other dependently typed languages like Coq and Agda offer features like corecursion and coinduction. Coinduction can be used to express a class of

*productive*non-terminating programs. For instance, using coinduction, one could program a web server that loops forever to handle an infinite stream of requests, while producing a response for each request in a finite amount of time. Even the`collatz`

function can be given a corecursive definition that computes a potentially infinite stream of numbers. However, not all non-terminating computations can be implemented with coinduction/corecursion. F* does not yet support coinduction.

## The `Dv`

effect

The effect `Dv`

(for divergence) is a primitive effect in F*.
Computations in `Dv`

may not terminate, even with infinite
resources. In other words, computations in the `Dv`

effect have the
observational behavior of non-termination. For example, the following
`loop`

function has type `unit -> Dv unit`

and it always diverges
when called:

```
let rec loop (): Dv unit = loop ()
```

If we remove the `Dv`

effect label annotation, then F* treats the
function as total and will try to prove that the recursive call
terminates, according to its usual termination checking rules, i.e.,
F* will attempt to prove `() << ()`

which fails, as expected.

Since the `Dv`

effect admits divergence, F* essentially turns-off
the termination checker when typechecking `Dv`

computations. So the
recursive `loop ()`

call does not require a decreasing termination
metric.

## Partial correctness semantics of `Dv`

The `Tot`

effect in F* has a *total correctness* semantics. That is,
if a term has type `e:Tot t`

, then `e`

terminates terminates and
produces a value of type `t`

.

Terms with type `Dv t`

have a *partial correctness* semantics. That
is, a term `e:Dv t`

, `e`

may either run forever, but if it
terminates then the resulting value has type `t`

.

Another perspective is that aside from disabling the termination
checking features of F*, all other type-checking constraints are
enforced on `Dv`

term. This means that one can still give
interesting sound, specifications to `Dv`

programs, e.g., the type
below proves that if the Collatz function terminates, then the last
element of the sequence is `1`

.

```
let rec collatz_ends_in_one (n:pos)
: Dv (l:list pos { Cons? l /\ last l == 1 })
= if n = 1 then [n]
else if n % 2 = 0
then n::collatz_ends_in_one (n / 2)
else n::collatz_ends_in_one (3 * n + 1)
```

If, for example, in the base case we were to return the empty list
`[]`

rather than `[n]`

, then F* would refuse to accept the
program, since the program could terminate while returning a value
that is not an element of the annotated return type.

## Isolating `Dv`

from the logical core

Since `Dv`

terms need not terminate, a program that always loops
forever can be given any return type. For instance, the program below
has return type `False`

:

```
let rec dv_false () : Dv False = dv_false()
```

Importantly, a term of type `Dv False`

should not be confused as a
*proof* of `False`

, since that would lead immediately to unsoundness
of F*’s logical core. In particular, it should be impossible to turn a
`e:Dv t`

into a term of type `Tot t`

. This is achieved by F*’s
effect system, which treats `Tot`

as a sub-effect of `Dv`

, i.e.,
`Tot < Dv`

, in the effect order. As explained in earlier, this ensures that no `Tot`

term can depend on a
`Dv`

term, maintaining soundness of the total correctness
interpretation of `Tot`

.

As an example, the following attempt to “cast” `dv_false`

to `Tot`

fails, as does trying to use `dv_false`

to produce incorrect proofs
of other types.

```
[@@expect_failure]
let tot_false : Tot False = dv_false()
[@@ expect_failure]
let bad_zero : Tot (y:int{y == 0}) = dv_false (); 1
```

While F* does not allow `Tot`

computations to depend on `Dv`

computations, going the other way is perfectly fine. Intuitively,
always terminating computations are potentially non-terminating. We
can think of it like a *weakening* of the specification:

```
let add_one (x:int) : int = x + 1
let add_one_div (x:int) : Dv int = add_one x
```

The effect system of F* automatically *lifts* `Tot`

computations
into `Dv`

, meaning that `Tot`

functions can be seamlessly used in
`Dv`

functions.

The weakening of `Tot`

terms to other effects is so pervasive in F*
that one hardly even thinks about it, e.g., in the `collatz`

program, sub-terms like `n / 2`

are in `Tot`

but are easily used
within a computation in the `Dv`

effect.

## No extrinsic proofs for `Dv`

computations

One important consequence of any effectful code, including `Dv`

,
being outside the logical core of F* is that it is not possible to do
extrinsic proofs about effectful
code. One cannot even state properties of `Dv`

computations in
specifications, since even specifications must be total. For example,
even stating the following lemma is illegal:

```
[@@expect_failure]
val collatz_property (n:pos)
: Lemma (Cons? (collatz n) /\ last (collatz n) = 1)
```

This is nonsensical in F* since writing `Cons? (collatz n)`

supposes
that `collatz n`

is *defined*, whereas it might actually just loop
forever.

The only way to state properties about divergent programs is to encode the property intrinsically in the computation type, as we saw above.

```
let last #a (l:list a { Cons? l }) : a = L.index l (L.length l - 1)
val collatz_ends_in_one (n:pos)
: Dv (l:list pos { Cons? l /\ last l == 1 })
```

### Exercise

Define a predicate `collatz_spec (n:pos) (l:list pos) : bool`

that
decides if `l`

is a valid Collatz sequence starting at `n`

.

Implement `val collatz' (n:pos) : Dv (l:list pos { collatz_spec n l })`

.

What does this type mean? Are there other ways to implement
`collatz'`

with the same type?

**Answer**

```
let rec collatz_spec (n:pos) (l:list pos)
: Tot bool (decreases l)
= match l with
| [] -> false
| hd :: tl ->
hd = n && (
if hd = 1 then tl = []
else if n%2 = 0 then collatz_spec (n/2) tl
else collatz_spec (3*n + 1) tl
)
// collatz' may loop forever on some inputs
// but if it completes it always returns a valid
// Collatz sequence
let rec collatz' (n:pos)
: Dv (l:list pos { collatz_spec n l } )
= if n = 1 then [n]
else if n % 2 = 0
then n::collatz' (n / 2)
else n::collatz' (3 * n + 1)
// here's another bogus implementation that always loops
let rec collatz'' (n:pos)
: Dv (l:list pos { collatz_spec n l } )
= collatz'' n
```

## General Recursive Types and Impredicativity with `Dv`

Aside from disabling the decreases metric on recursive functions in
`Dv`

, F* also disables two other forms of termination checking on
`Dv`

computations.

Recall from a previous chapter that
inductive type definitions are subject to the *strict positivity*
condition, since non-positive definitions allow the definition of
recursive types and non-terminating computations. However, since
computations in the `Dv`

effect are already allowed to loop forever,
the strict positivity condition can be relaxed when `Dv`

types are
involved. For example, one can define this:

```
noeq
type nonpos =
| NonPos : (nonpos -> Dv False) -> nonpos
let loop_nonpos' (f:nonpos) : Dv False =
let NonPos g = f in g f
let loop_nonpos () : Dv False = loop_nonpos' (NonPos loop_nonpos')
```

The type `nonpos`

is not strictly positive, since it appears to the
left of an arrow in a field of one of its constructors. Indeed, usingn
`nonpos`

it is possible to define (without using `let rec`

) an
infinitely looping program `loop_nonpos()`

—however, the type ```
Dv
False
```

tells us that this program may loop forever, and the infinite
loop is safely isolated from F*’s logical core.

The other place in F*’s type system where termination checking comes
into play is in the universe levels. As we
learned previously, the logical core of F* is organized into an
infinite hierarchy with copies of the F* type system arranged in a
tower of universes. This stratification is necessary to prevent
inconsistencies within the logical core. However, term in the `Dv`

effect are outside the logical core and, as such, restrictions on the
universe levels no longer apply. As the snippet below shows a total
function returning a type in universe `u#a`

resides in universe
`u#(a + 1)`

. However, a `Dv`

function returning a type in `u#a`

is just in universe `0`

, since the only way to obtain the type
`dv_type`

returns is by incurring a `Dv`

effect and moving outside
F*’s logical core.

```
let tot_type : Type u#(a + 1) = unit -> Tot (Type u#a)
let dv_type : Type0 = unit -> Dv (Type u#a)
```

## Top-level Effects

A top-level F* term is not meant to be effectful. If one defines the following term, F* accepts the term but raises a warning saying “Top-level let bindings must be total—this term may have effects”.

```
let inconsistent : False = loop_nonpos()
```

Top-level effects can be problematic for a few reasons:

The order of evaluation of the effects in top-level terms is undefined for programs with multiple modules—it depends on the order in which modules are loaded at runtime.

Top-level effects, particularly when divergence is involved, can render F*’s typechecking context inconsistent. For example, once

`inconsistent`

is defined, then any other assertion can be proven.let _ = let _ = FStar.Squash.return_squash inconsistent in assert false

Nevertheless, when used carefully, top-level effects can be useful, e.g., to initialize the state of a module, or to start the main function of a program. So, pay attention to the warning F* raises when you have a top-level effect and make sure you really know what you’re doing.

## Example: Untyped Lambda Calculus

In this section, we put together the various things we’ve learned
about `Dv`

computations to define several variants of an untyped
lambda calculus.

You can refer back to our prior development of the simply typed lambda calculus if you need some basic background on the lambda calculus.

### Interpreting Deeply Embedded Lambda Terms

We start by defining the syntax of untyped lambda terms, below. The
variables use the de Bruijn convention, where a index of a variable
counts the number of lambda-binders to traverse to reach its binding
occurrence. The `Lam`

case just has the body of the lambda term,
with no type annotation on the binder, and no explicit name for the
variable.

```
let var = nat
type term =
| Var : var -> term
| Int : int -> term
| Lam : term -> term
| App : term -> term -> term
```

As usual, we can define what it means to substitute a variable `x`

with a (closed) term `v`

in `t`

—this is just a regular `Tot`

function.

```
let rec subst (x:var) (v:term) (t:term)
: Tot term (decreases t) =
match t with
| Var y -> if x = y then v else t
| Int _ -> t
| Lam t -> Lam (subst (x + 1) v t)
| App t0 t1 -> App (subst x v t0) (subst x v t1)
```

Finally, we can define an interpreter for `term`

, which can
(intentionally) loop infinitely, as is clear from the `Dv`

type
annotation.

```
(* This interpreter can (intentionally) loop infinitely *)
let rec interpret (t:term)
: Dv (option term)
= match t with
| Var _
| Int _
| Lam _ -> Some t
| App t0 t1 ->
let head = interpret t0 in
match head with
| None -> None
| Some (Lam body) -> interpret (subst 0 t1 body)
| _ -> None //type error, expected a function
(* (\x. x x) (\x. x x) *)
let loops () : Dv _ = interpret (App (Lam (App (Var 0) (Var 0)))
(Lam (App (Var 0) (Var 0))))
```

#### Exercise

This exercise is designed to show how you can prove non-trivial
properties of `Dv`

computations by giving them interesting dependent
types.

The substitution function defined here is only sound when the term being substituted is closed, otherwise, any free variables it has can be captured when substituted beneath a lambda.

A term is closed if it satisfies this definition:

```
let rec closed' (t:term) (offset:int)
: bool
= match t with
| Int _ -> true
| Var i -> i <= offset
| Lam t -> closed' t (offset + 1)
| App t0 t1 -> closed' t0 offset && closed' t1 offset
let closed t = closed' t (-1)
```

Restrict the type of `subst`

so that its argument is ```
v : term {
closed v }
```

—you will have to also revise the type of its other
argument for the proof to work.

Next, give the following type to the interpreter itself, proving that interpreting closed terms produces closed terms, or loops forever.

```
let rec interpret (t:term { closed t })
: Dv (option (t:term { closed t }))
= match t with
| Int _
| Lam _ -> Some t
| App t0 t1 ->
let head = interpret t0 in
match head with
| None -> None
| Some (Lam body) -> interpret (subst 0 t1 body)
| _ -> None //type error, expected a function
```

**Answer**

```
module Part4.UTLCEx1
let var = nat
type term =
| Var : var -> term
| Int : int -> term
| Lam : term -> term
| App : term -> term -> term
//SNIPPET_START: closed$
let rec closed' (t:term) (offset:int)
: bool
= match t with
| Int _ -> true
| Var i -> i <= offset
| Lam t -> closed' t (offset + 1)
| App t0 t1 -> closed' t0 offset && closed' t1 offset
let closed t = closed' t (-1)
//SNIPPET_END: closed$
let rec closed'_weaken (t:term) (offset offset':int)
: Lemma
(requires closed' t offset /\
offset <= offset')
(ensures closed' t offset')
= match t with
| Int _ -> ()
| Var _ -> ()
| Lam t -> closed'_weaken t (offset + 1) (offset' + 1)
| App t0 t1 ->
closed'_weaken t0 offset offset';
closed'_weaken t1 offset offset'
let rec subst (x:var)
(v:term { closed v })
(t:term { closed' t x })
: Tot (t1:term { closed' t1 (x - 1) }) (decreases t) =
match t with
| Var y -> if x = y then (closed'_weaken v (-1) (x - 1); v) else t
| Int _ -> t
| Lam t -> Lam (subst (x + 1) v t)
| App t0 t1 -> App (subst x v t0) (subst x v t1)
//SNIPPET_START: interpret$
let rec interpret (t:term { closed t })
: Dv (option (t:term { closed t }))
= match t with
| Int _
| Lam _ -> Some t
| App t0 t1 ->
let head = interpret t0 in
match head with
| None -> None
| Some (Lam body) -> interpret (subst 0 t1 body)
| _ -> None //type error, expected a function
//SNIPPET_END: interpret$
```

### Denoting Lambda Terms into an F* Recursive Type

We now look at a variation on the interpreter above to illustrate how
(non-positive) recursive types using `Dv`

can also be used to give a
semantics to untyped lambda terms.

Consider the type `dyn`

shown below—it has a non-positive
constructor `DFun`

. We can use this type to interpret untyped lambda
terms into dynamically typed, potentially divergent, F* terms,
showing, in a way, that untyped lambda calculus is no more expressive
than F* with the `Dv`

effect.

```
noeq
type dyn =
| DErr : string -> dyn
| DInt : int -> dyn
| DFun : (dyn -> Dv dyn) -> dyn
```

The program `denote`

shown below gives a semantics to `term`

using
`dyn`

. It is parameterized by a `ctx : ctx_t`

, which interprets
the free variables of the term into `dyn`

.

```
let ctx_t = nat -> dyn
let shift (ctx:ctx_t) (v:dyn)
: ctx_t
= fun n -> if n = 0 then v else ctx (n - 1)
let rec denote (t:term) (ctx:ctx_t)
: Dv dyn
= match t with
| Var v -> ctx v
| Int i -> DInt i
| Lam t -> DFun (fun v -> denote t (shift ctx v))
| App t0 t1 ->
match denote t0 ctx with
| DFun f -> f (denote t1 ctx)
| DErr msg -> DErr msg
| DInt _ -> DErr "Cannot apply an integer"
```

We look at the cases in detail:

In the

`Var`

case, the intepretation just refers to the context.Integers constants in

`term`

are directly interpreted to integers in`dyn`

.The case of

`Lam`

is the most interesting: An lambda abstraction in`term`

is interpreted as an F* function`dyn -> Dv dyn`

, recursively calling the denotation function on the body when the function is applied. Here’s where we see the non-positivity of`DFun`

at play—it allows us to inject the function into the`dyn`

type.Finally, in the application case, we interpret a syntactic application in

`term`

as function application in F* (unless the head is not a function, in which case we have a type error).

#### Exercise

This exercise is similar in spirit to the previous one and designed to
show that you can prove some simple properties of `denote`

by
enriching its type.

Can you prove that a closed term can be interpreted in an empty context?

First, let’s refine the type of contexts so that it only provides an interpretation to only some variables:

```
let ctx_t (i:int) = x:nat{x <= i} -> dyn
```

Next, let’s define `free t`

to compute the greatest index of a free
variable in a term.

```
let max (x y:int) : int = if x < y then y else x
let rec free (t:term)
: int
= match t with
| Var x -> x
| Int _ -> -1
| Lam t -> free t - 1
| App t0 t1 -> max (free t0) (free t1)
```

Can you give the same `denote`

function shown earlier the following
type?

```
val denote (t:term) (ctx:ctx_t (free t))
: Dv dyn
```

Next, define the empty context as shown below:

```
let empty_context : ctx_t (-1) = fun _ -> false_elim ()
```

Given a closed term `t : term { closed t }`

, where ```
closed t =
(free t = -1)
```

, can you use `denote`

to give an interpretation to
closed terms in the empty context?

**Answer**

```
module Part4.UTLCEx2
let var = nat
type term =
| Var : var -> term
| Int : int -> term
| Lam : term -> term
| App : term -> term -> term
//SNIPPET_START: free$
let max (x y:int) : int = if x < y then y else x
let rec free (t:term)
: int
= match t with
| Var x -> x
| Int _ -> -1
| Lam t -> free t - 1
| App t0 t1 -> max (free t0) (free t1)
//SNIPPET_END: free$
noeq
type dyn =
| DErr : string -> dyn
| DInt : int -> dyn
| DFun : (dyn -> Dv dyn) -> dyn
//SNIPPET_START: ctx_t$
let ctx_t (i:int) = x:nat{x <= i} -> dyn
//SNIPPET_END: ctx_t$
let shift #i (ctx:ctx_t i) (v:dyn)
: ctx_t (i + 1)
= fun n -> if n = 0 then v else ctx (n - 1)
(* This is similar to the interpreter, but
"interprets" terms into the F* type dyn
rather than just reducing syntax to syntax *)
let rec denote (t:term)
(ctx:ctx_t (free t))
: Dv dyn
= match t with
| Var v -> ctx v
| Int i -> DInt i
| Lam t -> DFun (fun v -> denote t (shift ctx v))
| App t0 t1 ->
match denote t0 ctx with
| DFun f -> f (denote t1 ctx)
| DErr msg -> DErr msg
| DInt _ -> DErr "Cannot apply an integer"
//SNIPPET_START: empty_context$
let empty_context : ctx_t (-1) = fun _ -> false_elim ()
//SNIPPET_END: empty_context$
let closed t = free t = -1
let denote_closed (t:term { closed t })
: Dv dyn
= denote t empty_context
```

### Shallowly Embedded Dynamically Typed Programming

In the previous example, we saw how the syntax of untyped lambda terms
can be interpreted into the F* type `dyn`

. In this example, rather
than going via the indirection of the syntax of lambda terms, we show
how the type `dyn`

can be used directly to embed within F* a small
Turing complete, dynamically typed programming language.

We can start by lifting the F* operations on integers and functions to
(possibly failing) operations on `dyn`

.

```
(* Lifting operations on integers to operations on dyn *)
let lift (op: int -> int -> int) (n m:dyn) : dyn
= match n, m with
| DInt i, DInt j -> DInt (op i j)
| _ -> DErr "Expected integers"
let mul = lift op_Multiply
let sub = lift op_Subtraction
let add = lift op_Addition
let div (n m:dyn)
= match n, m with
| DInt i, DInt j ->
if j = 0 then DErr "Division by zero"
else DInt (i / j)
| _ -> DErr "Expected integers"
let mod (n m:dyn)
= match n, m with
| DInt i, DInt j ->
if j = 0 then DErr "Division by zero"
else DInt (i % j)
| _ -> DErr "Expected integers"
```

We also encode provide operations to compare dyn-typed integers and to
branch on them, treating `0`

as `false`

.

```
(* Branching *)
let if_ (d:dyn) (then_ else_:dyn) =
match d with
| DInt b ->
if b<>0 then then_ else else_
| _ -> DErr "Can only branch on integers"
(* comparison *)
let eq_ (d:dyn) (d':dyn)
: dyn
= match d, d' with
| DInt i, DInt j -> DInt (if i = j then 1 else 0)
| _ -> DErr "Can only compare integers"
```

For functions, we can provide combinators to apply functions and,
importantly, a combinator `fix`

that provides general recursion.

```
(* Dynamically typed application *)
let app (f:dyn) (x:dyn)
: Dv dyn
= match f with
| DFun f -> f x
| _ -> DErr "Can only apply a function"
(* general recursion *)
let rec fix (f: (dyn -> Dv dyn) -> dyn -> Dv dyn) (n:dyn)
: Dv dyn
= f (fix f) n
```

An aside on the arity of recursive functions: You may wonder why
`fix`

is defined as shown, rather than `fix_alt`

below, which
removes a needless additional abstraction. The reason is that with
`fix_alt`

, to instruct F* to disable the termination checker on the
recursive definition, we need an additional `Dv`

annotation: indeed,
evaluating `fixalt f`

in a call-by-value semantics would result,
unconditionally, in an infinite loop, whereas `fix f`

would
immediately return the lambda term `fun n -> f (fix f) n`

. In other
words, eta reduction (or removing redundant function applications)
does not preserve semantics in the presence of divergence.

```
let rec fix_alt (f: (dyn -> Dv dyn) -> dyn -> Dv dyn)
: Dv (dyn -> Dv dyn)
= f (fix_alt f)
```

With that, we can program non-trivial dynamically typed, general recursive programs within F* itself, as seen below.

```
(* shorthands *)
let i (i:int) : dyn = DInt i
let lam (f:dyn -> Dv dyn) : dyn = DFun f
(* a dynamically typed analog of collatz *)
let collatz_dyn
: dyn
= lam (fix (fun collatz n ->
if_ (eq_ n (i 1))
(i 1)
(if_ (eq_ (mod n (i 2)) (i 0))
(collatz (div n (i 2)))
(collatz (add (mul n (i 3)) (i 1))))))
```

All of which is to illustrate that with general recursion and
non-positive datatypes using `Dv`

, F* is a general-purpose
programming language like ML, Haskell, Lisp, or Scheme, or other
functional languages you may be familiar with.