foetus - termination checker for simple functional programs

24
foetus - Termination Checker for Simple Functional Programs Andreas Abel * July 16, 1998 Abstract We introduce a simple functional language foetus (lambda calculus with tuples, constructors and pattern matching) supplied with a ter- mination checker. This checker tries to find a well-founded structural order on the parameters on the given function to prove termination. The components of the check algorithm are: function call extraction out of the program text, call graph completion and finding a lexical order for the function parameters. The HTML version of this paper contains many ready-to-run Web-based examples. 1 Introduction Since the very beginning of informatics the problem of termination has been of special interest, for it is part of the problem of program verification for in- stance. Because the halting problem is undecidable, there is no method that can prove or disprove termination of all programs, but for several systems termination checkers have been developed. We have focused on functional programs and designed the simple language foetus 1 , for which we have imple- mented a termination prover. foetus is a simplification of MuTTI (Munich Type Theory Implementation) based on partial Type Theory (ala Martin * Theoretical Computer Science, Institute of Computer Science, Ludwigs- Maximilians-University, Oettingenstr. 67, D-80538 Munich, Germany, email: [email protected]. I want to thank my supervisor Thorsten Al- tenkirch and Rolf Backofen for his friendly support in technical questions. 1 In German foetus is an abbreviation of “Funktionale – Obgleich Eingeschr¨ ankt – Ter- mination Untersuchende Sprache” ;-). It also expresses that it is derived from MuTTI (this is the German term for Mum). 1

Upload: others

Post on 24-Dec-2021

6 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: foetus - Termination Checker for Simple Functional Programs

foetus - Termination Checker for SimpleFunctional Programs

Andreas Abel∗

July 16, 1998

Abstract

We introduce a simple functional language foetus (lambda calculuswith tuples, constructors and pattern matching) supplied with a ter-mination checker. This checker tries to find a well-founded structuralorder on the parameters on the given function to prove termination.The components of the check algorithm are: function call extractionout of the program text, call graph completion and finding a lexicalorder for the function parameters. The HTML version of this papercontains many ready-to-run Web-based examples.

1 Introduction

Since the very beginning of informatics the problem of termination has beenof special interest, for it is part of the problem of program verification for in-stance. Because the halting problem is undecidable, there is no method thatcan prove or disprove termination of all programs, but for several systemstermination checkers have been developed. We have focused on functionalprograms and designed the simple language foetus1, for which we have imple-mented a termination prover. foetus is a simplification of MuTTI (MunichType Theory Implementation) based on partial Type Theory (ala Martin

∗Theoretical Computer Science, Institute of Computer Science, Ludwigs-Maximilians-University, Oettingenstr. 67, D-80538 Munich, Germany, email:[email protected]. I want to thank my supervisor Thorsten Al-tenkirch and Rolf Backofen for his friendly support in technical questions.

1In German foetus is an abbreviation of “Funktionale – Obgleich Eingeschrankt – Ter-mination Untersuchende Sprache” ;-). It also expresses that it is derived from MuTTI(this is the German term for Mum).

1

Page 2: foetus - Termination Checker for Simple Functional Programs

2 FOETUS LANGUAGE 2

Lof) extended by tuples, constructors and pattern matching. For the syntaxsee section 2.1.

To prove the termination of a functional program there has to be a wellfounded order on the product of the function parameters such that the argu-ments in each recursive call are smaller than the corresponding input regard-ing this order. We have limited to structural orderings. foetus tries to findsuch an order by collecting all recursive calls of the given function and thebelonging behaviour of the function arguments. To handle mutually recursivefunctions a call graph is constructed and completed.

Section 2 introduces the foetus “body” (syntax and type system). Sec-tion 3 provides some examples to intuitively learn the language and see theinterpreter and termination checker work. Then in section 4 we explain the“heart” of foetus: the call extractor; we also informally introduce call graphcompletion and finding of the lexical order: the “brain” of foetus. The latteris formally described in section 5.

2 foetus Language

2.1 foetus Syntax

A foetus program consists of terms and definitions.

P :: empty program| term; P term to be evaluated| definition; P definition for further use

When processing input, foetus evaluates the terms and stores the definitionsin the environment. “Reserved words” in the foetus language are case, of,let and in. Special characterss are ( ) [ ] { } | . , ; = =>. An iden-tifier may contain letters, digits, apostrophies and underscores. If it startswith a small letter it stands for a variable, else it denotes a constant.

Term syntax. In the following x, x1, x2, . . . denote variables, C, C1, C2,. . . constants and u, t, t1, t2, . . . foetus terms.

Page 3: foetus - Termination Checker for Simple Functional Programs

2 FOETUS LANGUAGE 3

t :: x variable| [x]t lambda| tu application| C(t) constructor| case t of { C1x1 ⇒ t1| . . . |Cnxn ⇒ tn } pattern matching| (C1 = t1, . . . , Cn = tn) tuple| t.C projection| let x1 = t1, . . . , xn = tn in t let| (t) (extra parentheses)

Definitions. A definition statement has the form x1 = t1, . . . , xn = tn(it is a let-term without a “body”). All variables x1, . . . , xn are definedsimultanously, thus can refer to each other.

Example. The following foetus program defines addition on natural num-bers (spanned by the two constructors O “zero” and S “successor”) and cal-culates 1 + 1.

add = [x][y]case x of

{ O z => y

| S x’ => S(add x’ y) };

one = S(O());

add one one;

Note that although O is a zero-argument-constructor the syntax forces usto supply a dummy variable z within the pattern definition and also emptytuple () in the definition of one.

2.2 foetus Type System

In the following x, x1, x2, . . . denote variables, C, C1, C2, . . . constants, u, t,t1, t2, . . . foetus terms, τ , σ, σ1, σ2, . . . foetus types and X, X1, X2, . . . typevariables. Γ = x1 : σ1, . . . , xn : σn denotes the context. The judgement

Γ ` t : σ

means “in context Γ term t is of type σ”.

Page 4: foetus - Termination Checker for Simple Functional Programs

2 FOETUS LANGUAGE 4

Type formation.

τ :: σ → τ →-type| {C1 : σ1| . . . |Cn : σn} labeled sum type| (C1 : σ1, . . . , Cn : σn) labeled product type| {X}τ polymorphic type| τσ instantiation of polymorphic type| Let X1 = σ1, . . . , Xn = σn in τ recursive type

In the formation of a recursive type with Let Xi may only appear strictpositiv in σi. We define congruence on types ∼= as the smallest congruenceclosed under

Let ~X = ~σ in τ ∼= τ [X1 := Let ~X = ~σ in X1; . . . ;Xn := Let ~X = ~σ in Xn]

( ~X = ~σ abbreviates X1 = σ1, . . . , Xn = σn). Thus we can substitute congru-ent types:

Γ ` t : σ σ ∼= τ

Γ ` t : τ

For ploymorphic types we have rules like in System F:

Γ ` t : σ X not free type variable in Γpoly − i

Γ ` t : {X}σ

Γ ` t : {X}σpoly − e

Γ ` t : σ[X := τ ]

2.3 Typing rules for foetus terms

We here only briefly introduce the typing rules. For more detailed explana-tion, read a book about type theorie, e.g. [NPS90].

Lambda abstraction.

Γ, x : σ ` t : τ→ −i

Γ ` [x]t : σ → τ

Application.Γ ` t : σ → τ Γ ` u : σ

→ −eΓ ` tu : σ

Page 5: foetus - Termination Checker for Simple Functional Programs

2 FOETUS LANGUAGE 5

Constructor.

Γ ` t : σi{} − i

Γ ` Ci(t) : {C1 : σ1| . . . |Cn : σn}

Pattern matching.

Γ ` t : {C1 : σ1| . . . |Cn : σn} Γ, xi : σi ` ui : σ for all 1 ≤ i ≤ n{} − e

Γ ` case t of {C1(x1)⇒ u1| . . . |Cn(xn)⇒ un} : σ

Tupels.

Γ ` ti : σi for all 1 ≤ i ≤ n()− i

Γ ` (C1 = t1, . . . , Cn = tn) : (C1 : σ1, . . . , Cn : σn)

Projection.

Γ ` t : (C1 : σ1, . . . , Cn : σn)()− e

Γ ` t.Ci : σi

Let.

Γ, x1 : σ1, . . . , xn : σn ` ti : σi for all 1 ≤ i ≤ n Γ, x1 : σ1, . . . , xn : σn ` u : τlet

Γ ` let x1 = t1; . . . ;xn = tn in u : τ

In foetus type checking is not yet implemented and it is assummed thatall terms entered are well typed. Of course, only for well typed terms thetermination check produces valid results.

Example. The following well-known example for non-termination passesthe foetus termination checker, but it is not well typed.

f = [x]x x;

a = f f;

foetus output:

f passes termination check

a passes termination check

Page 6: foetus - Termination Checker for Simple Functional Programs

3 EXAMPLES 6

3 Examples

3.1 Addition and multiplication

On the natural numbers

nat := Let nat = {O()|S(nat)} in nat

we define add, mult : nat→ nat→ nat:

add = [x][y]case x of

{ O z => y

| S x’ => S(add x’ y) };

mult = [x][y]case x of

{ O z => O z

| S x’ => (add y (mult x’ y)) };

add (S(S(O()))) (S(O()));

mult (S(S(O()))) (S(S(S(O()))));

foetus output:

< =: add -> add

add passes termination check by lexical order 0

< =: mult -> mult

mult passes termination check by lexical order 0

result: S(S(S(O())))

result: S(S(S(S(S(S(O()))))))

3.2 Subtraction

We define the predecessor function p : nat→ nat and substraction on natural

numbers sub : nat→ nat→ nat. Note sub x y calculates y·− x.

p = [x]case x of { O z => O z | S x’ => x’ };

sub = [x][y]case x of

{ O z => y

| S x’ => sub x’ (p y) };

sub (S(S(O()))) (S(S(S(S(O())))));

foetus output:

p passes termination check

< ?: sub -> sub

sub passes termination check by lexical order 0

result: S(S(O()))

Page 7: foetus - Termination Checker for Simple Functional Programs

3 EXAMPLES 7

3.3 Division

Division div : nat→ nat→ nat can be implemented as follows in functionallanguages (note that div x y calculates b y

xc):

div (x,y) = div’(x,y+1-x)

div’(x,y) = if (y=0) then 0 else div’(x,y-x)

div’ (just like division on natural numbers) terminates if the divisor x isunequal 0 because then y-x < y in the recursive call and thus one functionargument is decreasing. But foetus recognizes only direct structural decreaseand cannot see that sub x y’ is less than y’. To prove termination of div’you need a proof for x 6= 0→ subx y < y [BG96].

p = [x]case x of { O z => O z | S x’ => x’ };

sub = [x][y]case x of

{ O z => y

| S x’ => sub x’ (p y) };

div = [x][y]let

div’ = [y’]case y’ of

{ O z => O z

| S dummy => S(div’ (sub x y’)) }

in

(div’ (sub (p x) y));

div (S(S(O()))) (S(S(S(S(S(O()))))));

foetus output:

p passes termination check

< ?: sub -> sub

sub passes termination check by lexical order 0

div passes termination check

?: div’ -> div’

div’ FAILS termination check

result: S(S(O()))

Here foetus says div’ fails termination check, so div will not terminate either.div would terminate, if div’ terminated, therefore you get the answer div

passes termination check.

3.4 Ackermann function

The not primitive recursive Ackermann function ack : nat→ nat→ nat.

Page 8: foetus - Termination Checker for Simple Functional Programs

3 EXAMPLES 8

ack = [x][y]case x of

{ O z => S(y)

| S x’ => ack x’ (case y of

{ O z => S(O())

| S y’ => ack x y’} ) };

ack (S(S(O()))) (O());

foetus output:

foetus $Revision: 1.0 $

= <: ack -> ack

< ?: ack -> ack

ack passes termination check by lexical order 0 1

result: S(S(S(O())))

3.5 List processing

We define lists over type α as

list := {α}Let list = {Nil()|Cons(HD : α, TL : list)} in list

The well-known list processing functions map : (α→ β)→ listα→ listβ andfoldl : (α→ β → β)→ β → listα→ β are implemented and testet.

nil = Nil();

cons = [hd][tl]Cons(HD=hd,TL=tl);

l1 = cons (A()) (cons (B()) (cons (C()) nil));

map = [f][list]let

map’ = [l]case l of

{ Nil z => Nil()

| Cons pair => Cons (HD=(f pair.HD),

TL=(map’ pair.TL))}

in map’ list;

map ([el]F(el)) l1;

foldl = [f][e][list]let

foldl’ = [e][l]case l of

{ Nil z => e

| Cons p => foldl’ (f p.HD e) p.TL }

in foldl’ e list;

rev = [list]foldl cons nil list;

rev l1;

Page 9: foetus - Termination Checker for Simple Functional Programs

3 EXAMPLES 9

foetus output:

nil passes termination check

cons passes termination check

l1 passes termination check

map passes termination check

<: map’ -> map’

map’ passes termination check by lexical order 0

result: Cons(HD=F(A()), TL=Cons(HD=F(B()), TL=Cons(HD=F(C()),

TL=Nil())))

foldl passes termination check

? <: foldl’ -> foldl’

foldl’ passes termination check by lexical order 1

rev passes termination check

result: Cons(HD=C(), TL=Cons(HD=B(), TL=Cons(HD=A(), TL=Nil())))

3.6 List flattening

The task is to transform a list of lists into a list, so that the elements ofthe first list come first, then the elements of the second list and so on.Example: flatten [[A,B,C],[D,E,F]] = [A,B,C,D,E,F]. The first ver-sion flatten : list(listα) → listα works but fails termination check be-cause of the limited pattern matching abilities of foetus, but it is also badstyle and inefficient because it builds a temporary list for the recursive call.However, the second version f with a mutual recursive auxiliary functiong : listα→ list(listα)→ listα passes termination check.

nil = Nil();

cons = [hd][tl]Cons(HD=hd,TL=tl);

l1 = cons (A()) (cons (B()) (cons (C()) nil));

ll = (cons l1 (cons l1 nil));

flatten = [listlist]case listlist of

{ Nil z => Nil()

| Cons p => case p.HD of

{ Nil z => flatten p.TL

| Cons p’ => Cons(HD=p’.HD, TL=flatten

(Cons(HD=p’.TL, TL=p.TL))) }};

flatten ll;

Page 10: foetus - Termination Checker for Simple Functional Programs

3 EXAMPLES 10

f = [l]case l of

{ Nil z => Nil()

| Cons p => g p.HD p.TL },

g = [l][ls]case l of

{ Nil z => f ls

| Cons p => Cons(HD=p.HD, TL=(g p.TL ls)) };

f ll;

foetus output:

nil passes termination check

cons passes termination check

l1 passes termination check

ll passes termination check

?: flatten -> flatten

<: flatten -> flatten

flatten FAILS termination check

result: Cons(HD=A(), TL=Cons(HD=B(), TL=Cons(HD=C(),

TL=Cons(HD=A(), TL=Cons(HD=B(), TL=Cons(HD=C(), TL=Nil()))))))

<: f -> g -> f

f passes termination check by lexical order 0

? <: g -> f -> g

< =: g -> g

g passes termination check by lexical order 1 0

result: Cons(HD=A(), TL=Cons(HD=B(), TL=Cons(HD=C(),

TL=Cons(HD=A(), TL=Cons(HD=B(), TL=Cons(HD=C(), TL=Nil()))))))

3.7 Merge sort

With typebool := {True()|False()}

we can define le nat: nat → nat → bool and merge : (α → α → bool) →listα→ listα→ listα as follows:

merge = [le][l1][l2]case l1 of

{ Nil z => l2

| Cons p1 => case l2 of

{ Nil z => l1

| Cons p2 => case (le p1.HD p2.HD) of

Page 11: foetus - Termination Checker for Simple Functional Programs

3 EXAMPLES 11

{ True z => Cons(HD=p1.HD,

TL=merge le p1.TL l2)

| False z => Cons(HD=p2.HD,

TL=merge le l1 p2.TL) }}};

le_nat = [x][y]case x of

{ O z => True()

| S x’ => case y of

{ O z => False()

| S y’ => le_nat x’ y’ }};

i = S(O());

ii = S(S(O()));

iii = S(S(S(O())));

iv = S(S(S(S(O()))));

v = S(S(S(S(S(O())))));

l1 = Cons(HD=O(), TL=Cons(HD=iii, TL=Cons(HD=iv, TL=Nil())));

l2 = Cons(HD=i, TL=Cons(HD=ii, TL=Cons(HD=v, TL=Nil())));

merge le_nat l1 l2;

foetus output:

= < <: merge -> merge -> merge

= = <: merge -> merge

= < =: merge -> merge

merge passes termination check by lexical order 1 2

< <: le_nat -> le_nat

le_nat passes termination check by lexical order 0

result: Cons(HD=O(), TL=Cons(HD=S(O()), TL=Cons(HD=S(S(O())),

TL=Cons(HD=S(S(S(O()))), TL=Cons(HD=S(S(S(S(O())))),

TL=Cons(HD=S(S(S(S(S(O()))))), TL=Nil()))))))

3.8 Parameter permutation: list zipping

The following function zip : listα → listα → listα combines two lists intoone by alternately taking the first elements form these lists and putting theminto the result list.

zip = [l1][l2]case l1 of

{ Nil z => l2

| Cons p1 => Cons(HD=p1.HD, TL=zip l2 p1.TL) };

Page 12: foetus - Termination Checker for Simple Functional Programs

3 EXAMPLES 12

zip (Cons(HD=A(), TL=Cons(HD=C(), TL=Nil())))

(Cons(HD=B(), TL=Cons(HD=D(), TL=Nil())));

foetus output:

? ?: zip -> zip -> zip -> zip

< <: zip -> zip -> zip

? ?: zip -> zip

zip FAILS termination check

result: Cons(HD=A(), TL=Cons(HD=B(), TL=Cons(HD=C(),

TL=Cons(HD=D(), TL=Nil()))))

Here in the recursion of zip one arguments is decreasing, but arguments areswitched. Thus only a even number of recursive calls produces a structuraldecrease on l1 and l2. foetus does not recognize zip to be terminatingbecause not every (direct or indirect) recursive call makes the argumentssmaller on any structural lexical order.

Of course there are simple orders that fulfill the demanded criteria, like< on |l1|+ |l2|. Another solution is to “copy” zip into zip’ and implementmutual recursion as follows:

zip = [l1][l2]case l1 of

{ Nil z => l2

| Cons p1 => Cons(HD=p1.HD, TL=zip’ l2 p1.TL) },

zip’= [l1][l2]case l1 of

{ Nil z => l2

| Cons p1 => Cons(HD=p1.HD, TL=zip l2 p1.TL) };

zip (Cons(HD=A(), TL=Cons(HD=C(), TL=Nil())))

(Cons(HD=B(), TL=Cons(HD=D(), TL=Nil())));

foetus output:

< <: zip -> zip’ -> zip

zip passes termination check by lexical order 0

< <: zip’ -> zip -> zip’

zip’ passes termination check by lexical order 0

result: Cons(HD=A(), TL=Cons(HD=B(), TL=Cons(HD=C(), TL=Cons(HD=D(),

TL=Nil()))))

3.9 Tuple parameter

This example, an alternative version of add : (X : nat, Y : nat) → nat, showsthat foetus loses dependency information if you “pack” and “unpack” tuples.

Page 13: foetus - Termination Checker for Simple Functional Programs

3 EXAMPLES 13

add = [xy]case xy.X of

{ O z => xy.Y

| S x’ => S(add (X=x’, Y=xy.Y)) };

foetus output:

?: add -> add

add FAILS termination check

3.10 Transfinite addition of ordinal numbers

The type of ordinal numbers is

ord := Let ord = {O()|S(ord)|Lim(nat→ ord)} in ord

and addord : ord→ ord→ ord can be implemented as follows:

addord = [x][y]case x of

{ O o => y

| S x’ => S(addord x’ y)

| Lim f => Lim([z]addord (f z) y) };

foetus output:

< =: addord -> addord

addord passes termination check by lexical order 0

3.11 Fibonacci numbers

Iterative version fib : nat → nat of algorithm to calculate the fibonaccinumbers fib(0) = 1, fib(1) = 1, 2, 3, 5, 8, . . .. Only the first parameter isimportant for termination, the second and the third parameter are “accumu-lators”.

fib’ = [n][fn][fn’]case n of

{ O z => fn

| S n’ => fib’ n’ (add fn fn’) fn};

fib = [n]fib’ n (S(O())) (O());

foetus output:

< ? ?: fib’ -> fib’ -> fib’

< ? ?: fib’ -> fib’

fib’ passes termination check by lexical order 0

fib passes termination check

Page 14: foetus - Termination Checker for Simple Functional Programs

3 EXAMPLES 14

3.12 Non-terminating mutual recursion

The following three functions f, g, h : nat → nat → nat are an artificialexample for non-termination that has been designed to show to what extentthe call graph has to be completed to assure correct results of the terminationchecker. Function h (here h(x, y) = 0 ∀x, y) could be any function that “looksinto” its arguments, e.g. add.

h = [x][y]case x of

{ O z => case y of

{ O z => O()

| S y’ => h x y’ }

| S x’ => h x’ y },

f = [x][y]case x of

{ O z => O()

| S x’ => case y of

{ O z => O()

| S y’ => h (g x’ y) (f (S(S(x))) y’) } },

g = [x][y]case x of

{ O z => O()

| S x’ => case y of

{ O z => O()

| S y’ => h (f x y) (g x’ (S(y))) } };

(* f (S(S(O()))) (S(S(O()))); *)

foetus output: Note that the combined call f → g → f still does notprevent termination. But then call graph completion finds f → g → g → fthat destroys the lexical order 1 0 that was possible until then.

< <: h -> h -> h

< =: h -> h

= <: h -> h

h passes termination check by lexical order 0 1

< ?: f -> g -> g -> f

? ?: f -> f -> g -> g -> f

< =: f -> g -> f

? <: f -> f

f FAILS termination check

? <: g -> f -> f -> g

Page 15: foetus - Termination Checker for Simple Functional Programs

4 TERMINATION CHECKER OVERALL OUTLINE 15

? ?: g -> g -> f -> f -> g

< =: g -> f -> g

< ?: g -> g

g FAILS termination check

4 Termination Checker Overall Outline

4.1 Function call extraction

The task of foetus is to check whether functions terminate or not. Becausethe foetus language is functional and no direct loop constructs exist, theonly means to form loops is recursion. Therefore out of the program text allfunction calls have to be extracted to find direct or indirect recursive callsthat may cause termination problems.

The heart of foetus is a analyzer that runs through the syntax tree of thegiven foetus program and looks for applications. Consecutive applications aregathered and formed in to a function call, e.g. in example 3.1, function add.There the two applications ((add x’) y) form the call add(x′, y). As you seein this example “add” is always terminating because in each recursive call thefirst argument x is decreased. foetus stores with each call information abouthow the arguments of the call (x′, y in the example) relate to the parametersof the calling function (here: x, y), the so-called depedencies (here: x′ < x,y = y). We distinguish three kinds of relations: < (less), = (equal) and ?(unknown, this includes ‘greater’).

The abilities of foetus to recognise dependencies are yet very limited. Sofar only three cases are considered:

1. Constructor elimination.Be x, y variables and C a constructor, and x = C(y). It follows y < x.This is applied in case constructs (see example above).

2. Projection.Be x, y variables, L a label, ρ a relation in {<,=} and y ρ x. Hereit follows y.L ρ x, i.d. a component is considered as big as the entiretuple.

3. Application.Be x, y variables, a a vector of terms (arguments of y), ρ a relation in{<,=} and y ρ x. It follows (ya) ρ x.

The rule 3 may have a strange looking, but it can be applied in example 3.10(addord). In the third case x = Lim(f) we have with rule 1 f < x and withrule 3 (fz) < x, therefore addord is terminating.

Page 16: foetus - Termination Checker for Simple Functional Programs

5 FORMAL DESCRIPTION 16

4.2 Call graph

In the end the whole of extracted function calls form the call graph. It is amultigraph; each vertex represents a function and each edge from vertex f tovertex g a call of function g within the function of f . The edges are labeledwith the dependency information (see above) put in a call matrix. The callmatrix for the only one call add→ add in example 3.1 would be

x yx′ < ?y ? =

Note that each row represents one call argument and its relations to thecalling function parameters.

Now if a function f calls a function g and the latter calls another functionh, f indirectly calls h. The call matrix of this combined call f → h is theproduct of the two matrices of g → h and f → g. We get the completed callgraph if we insert all combined calls (as new edges) into the original graph.

To find out whether a function f is terminating you have to collect all callsfrom f to itself out of the completed call graph (this includes the direct andthe indirect calls). When a lexical order exists on the function parameters off so that every recursive call decreases the order of the parameters, we haveproven the termination of f. This order we call termination order.

We could call the algorithms of call graph completion and finding a lexicalorder the “brain” of foetus; it is described more precisely and formally in thenext section.

5 Formal Description

5.1 Call Matrix

Be R = {<,=, ?} set of the relations “less than”, “equal to” and “relationunknown”. In the context of “f(x, y) calls g(a, b)” a < y means “we knowthat (call) argument a is less than (input) parameter y”, a = y means “a is(at least) equal to y (if not less than)” and a ? y means “we do not know therelation between a and y”.

With the two operations + and · defined as in table 1 R forms a com-mutative rig2 with 0-element ? and 1-element =. The operation + can beunderstood as “combining parallel information about a relation”, e.g. if we

2On the WWW I found the English term “rig” for what Germans call a “Halb-ring”. This is probably a play of words: Compared to a “ring” a “rig” missesan “n” as well as inverse elements regarding addition. I cite Ross Moore (see

Page 17: foetus - Termination Checker for Simple Functional Programs

5 FORMAL DESCRIPTION 17

have a ? y and a < y we have a (?+ <) y and that simplifies to a < y.The operation · however is “serial combination”, e.g. a < y and y = z canbe combined into a (< · =) z, simplified: a < z. ? is neutral regarding +because it gives you no new information, whereas < is dominant because itis the strongest information. Regarding · the relation = is neutral and ? isdominant because it “destroys” all information. Check the table to see whichrelation overrides which.

+ < = ?< < < <= < = =? < = ?

· < = ?< < < ?= < = ?? ? ? ?

Table 1: Operations on R

Now we can define multiplication on matrices over R as usual:

· : Rn×m ×Rm×l → Rn×l

((aij), (bij)) 7→ (cij) =

(m∑k=1

aikbkj

)Why is this a reasonable definition? Assume you have three sets of variables{x1, . . . , xn}, {y1, . . . , ym} and {z1, . . . , zl}, a matrix A = (aij) εR

n×m reflect-ing the relations between the xis and the yis (i.d. aij = ρ ⇐⇒ xi ρ yj) anda matrix B εRm×l reflecting the relations between the yis and the zis. Thenthe matrix product C = AB reflects the relations between the xis and thezis. Because

cij = ai1 · b1j + ai2 · b2j + . . .+ aim · bmj,we have e.g. xi < zj if we know it by intermediate variable y1 (ai1 ·b1j = ‘< ′)or by intermediate variable y2 or . . . (to be continued).

http://www.mpce.mq.edu.au/∼ross/maths/Quantum/Sect1.html#206):

A rig is a set R enriched with two monoid structures, a commutative onewritten additively and the other written multiplicatively, such that the fol-lowing equations hold:

a0 = 0 = 0a

a(b+ c) = ab+ ac, (a+ b)c = ac+ ab

The natural numbers N provide an example of a rig.A ring is a rig for which the additive monoid is a group. The integers Zprovide an example.A rig is commutative when the multiplicative monoid is commutative.

Page 18: foetus - Termination Checker for Simple Functional Programs

5 FORMAL DESCRIPTION 18

Definition. A call matrix is a matrix over R with no more than one elementdifferent from ? per row.

CM(n,m) := {(aij) εRn×m : ∀i∀j∀k 6= j(aij = ? ∨ aik = ?)}

Remark. The reason we define call matrices this way is these are the onlyones foetus produces by function call extraction (see section 4.1). Becausefoetus recognizes only the three described cases of dependecies, a call argu-ment can only depend of one function parameter. But multiple dependeciesare imaginable, like in

f(x,y) = if (x=0) then 0 else let a=min(x,y)-1 in f(a,x)

Here the second call argument a is less than both x and y. The next proposi-tion assures that all matrices foetus will have do deal with are call matrices.

Proposition. Matrix multiplication on matrices induces a multiplicationon call matrices

· : CM(n,m)× CM(m, l)→ CM(n, l)

This operation is well defined.

Proof. BeA = (aij) εCM(n,m), B = (bij) εCM(m, l), AB = C = (cij) εRn×l

and k(i) the index of the element of the ith row of A that is different to ?(or 1, if no such element exists). The we have with the rules in rig R

cij =m∑k=1

aikbkj = ai,k(i)bk(i),j

Now consider the ith row of C:

ci = (cij)1≤j≤l = (ai,k(i)bk(i),j)1≤j≤l

Because at most one bk(i),j is unequal to ?, at most one element of ci isunequal to ?. Therefore C εCM(n, l).

5.2 Call Graph

For each i εN we assume a set F (i) = {f (i), g(i), h(i), ...} of identifiers forfunctions of arity i, F =

⊎i εN F

(i).

Page 19: foetus - Termination Checker for Simple Functional Programs

5 FORMAL DESCRIPTION 19

Definition. We form the set of calls as follows

C = {(f (n), g(m), A) : f (n) ε F (n), g(m) ε F (m), A εCM(m,n)}

On calls we define the partial operation combination of calls

◦ : C × C → C((g(m), h(l), B), (f (n), g(m), A)

)7→ (f (n), h(l), BA)

Meaning: If g calls h with call matrix B and f calls g with call matrix A,then f indirectly calls h with call matrix BA. ◦ cannot be applied to callsthat have no “common function” like g, therefore it is partial. ◦ can beexpanded to sets of calls

◦ : P(C)× P(C)→ P(C)

(C,C ′) 7→ {c ◦C c′ : c ε C, c′ ε C ′, (c, c′) εDom(◦C)}

Here we combine each call in C with each call in C to which ◦C is applicableand form a set of the combined calls. ◦P(C) is a total function.

Definition. A call graph is a graph (V,E) with vertices V = F and edgesE ⊂finit C. A call graph is complete if

E ◦ E ⊆ E

Definition. The completion of a call graph (V,E) is a call graph (V,E ′)such that

(1) (V,E ′) is complete,

(2) E ⊆ E ′ and

(3) for all E ′′ satisfying (1) and (2) we have E ′ ⊆ E ′′.

Proposition. The completion of a call graph (V,E) is the call graph (V,E ′)such that

c εE ′ ⇐⇒ ∃n > 0, c1, . . . , cn εE : c1 ◦ . . . ◦ cn = c

Page 20: foetus - Termination Checker for Simple Functional Programs

5 FORMAL DESCRIPTION 20

Proof.

(1) Be c εE ′ ◦ E ′. Then there are d, e εE ′ with c = d ◦ e. Because (V,E ′)is complete, we have

d = d1 ◦ . . . ◦ dn d1, . . . , dn εE

e = e1 ◦ . . . ◦ em e1, . . . , em εE

Thus c = d1 ◦ . . . ◦ dn ◦ e1 ◦ . . . ◦ em εE ′.

(2) E ⊆ E ′ is trivial with n = 1.

(3) Be (V,E ′′) complete and E ⊆ E ′′. This gives us E◦E ⊆ E ′′ from whichwe gain by induction

E ◦ . . . ◦ E︸ ︷︷ ︸n−times

=: En ⊆ E ′′ for all n.

Now be c εE ′. That implies c = c1 ◦ . . . ◦ cn (ci εE) for a suitable n.Hence c εEn ⊆ E ′′. q · e · d

Proposition. (Completion algorithm) Be (V,E) a call graph, (V,E ′)its completion and (En)n εN a sequence of sets of calls defined as follows:

E0 = E

En+1 = En ∪ (En ◦ E)

Then there is a n εN so that

E ′ = En = En+1 = En+2 = . . .

(Obviously the En grow monotonously.)

Proof. First we show by induction that En ⊆ E ′ for all n εN: It is obviousthat E0 ⊆ E ′. Now be En ⊆ E ′ and c εEn+1 \En. Then c εEn ◦E, thereforec = d1 ◦ . . . ◦ dn ◦ e, d1, . . . , dn, e ε E. It follows c εE ′, En+1 ⊆ E ′.

Second: Because we have a finit set of starting edges E and therefore afinit set of reachable vertices and also a finit set of possible edges between twovertices (limited by the number of different call matrices of fixed dimensions)the Eis cannot grow endlessly. Thus an n εN exists with En = En+1.

Third: We show that E ′ ⊆ En for that particular n. Be c εE ′. Thenthere exists an m such that c = d1 ◦ . . . ◦ dm, therefore c εEm. Now if m ≤ nthen Em ⊆ En, otherwise m > n and hence Em = En, in both cases c εEn.q · e · d

Page 21: foetus - Termination Checker for Simple Functional Programs

5 FORMAL DESCRIPTION 21

5.3 Lexical Order

Definition. Be (V,E) a complete call graph and f (i) a function of arity i.We call

Ef (i) := {∆(C) : (f (i), f (i), C) εE} ⊂ Ri

the recursion behaviour of function f (i). (∆ takes the diagonal of squarematrices).

Each row of this set represents one possible recursive call of f (i) and howthe orders of all parameters are altered in this call. The diagonals of the callmatrices are taken because we want to know only how a parameter relatesto its old value in the last call to f (i). Ef (i) of course is a finite subset of Ri.

In the following we identify lexical orders on parameters with permuta-tions π ε Sn of the arguments. Often not all of the parameters are relevantfor termination; these are not listed in the lexical order and can appear inthe permutation in any sequence.

In example 3.11 (fib’) only the argument 0 has to be considered to provetermination, the order of argument 1 and 2 are irrelevant and therefore bothpermutations

π1 =

(0 1 20 1 2

)and

π2 =

(0 1 20 2 1

)are valid continuations of the lexical order “0”.

Note: In the following we abbreviate the notation of permutations to π1 =[012] and π2 = [021].

Definition. (1) Be B the recursion behaviour of function f (n). We callthe permutation π ε Sn a termination order for f (n) if

∀r εB∃1 ≤ k ≤ n : rπ(k) = ‘< ′ ∧ (∀1 ≤ i ≤ k : rπ(i) = ‘= ′)

This definition is a very wide one. In most cases you will look for morespecial termination orders:

Definition. (2, inductive) Be B the recursion behaviour of a given func-tion. We call the permutation π ε Sn a termination order on B if |B| = 0

Page 22: foetus - Termination Checker for Simple Functional Programs

6 IMPLEMENTATION 22

or

∃r εB : rπ(0) =<

∧ 6 ∃r εB : rπ(0) = ?

∧π′0 ε Sn−1 termination order on B′ := {r′π(0) : rπ(0) 6=<} ⊂ Rn−1

whereas π′i = [k0 . . . ki−1ki+1 . . . kn−1] ε Sn−1 given π = [k0 . . . kn−1] ε Sn andr′i = (k0, . . . , ki−1, ki+1, . . . , kn−1) εRn−1 given r = (k0, . . . , kn−1) εRn.

The algorithm implemented in foetus searches termination orders like indefinition (2); it is a one-to-one transfer of this definition. Every terminationorder matching definition (2) also matches definition (1) and it can easily beshown that if there is a termination order of type (1) there also exists on oftype (2).

Example 5.1 Be E = {(=,<, ?), (=,=,<), (=,<,=)} the given recur-sion behaviour. Then π1 = [012] is a type (1) termination order on E andπ2 = [120] is of both types.

6 Implementation

foetus has been implemented in SML 97. We have used the new StandardML Basis Library to ensure a safe and possibly optimized handling of stan-dard data structures like lists etc. The parser for the foetus terms has beencreated with ML-Lex and ML-Yacc. The ML implementation currently usedis Standard ML of New Jersey, Version 109.32.

foetus.lex foetus language token specification for ml-lexfoetus.grm foetus language grammar for ml-yaccaux.sml auxiliary functionsclosure.sml terms and environmentfoetus.sml values, evaluation function hnf, printingmatrix.sml polymorphic matrices with necessary operationssimpledeps.sml simple implementation of dependeciesanalyse.sml static analysis of foetus codecheck.sml termination check via call graphtop.sml top level environmentload.sml loader and foetus parser

Table 2: foetus source files

Page 23: foetus - Termination Checker for Simple Functional Programs

7 CONCLUSION 23

7 Conclusion

We have seen that foetus and its “brain”, the call graph completion andfinding a lexical order on the function arguments, contributes to automatedtermination proofs. Of course, in its current state it is no more than a toyto gather experience on his subject. Some improvements have to be done:foetus should be able to recognize more kinds of dependencies (see section4.1).

• Let assignments. The use of let-constructs to save values within func-tions is discouraged because foetus stores no relations concerning them;it performs no symbolic evaluation during analyzation. For example:

case list of

{Cons pair => let

hd = pair.HD,

tl = pair.TL in ...

foetus does not know that hd < list and that tl < list. At leastsuch simple assignments (for code shortening) should be handled.

• Tuple handling. foetus should trace the dependencies not only of thewhole tuples but also of their components. At the moment you can-not define functions with one tuple as parameter instead of separateparameters and still expect a termination proof (see example 3.9).

• Function results. The reason that foetus cannot prove termination ofdiv (see example 3.3) is that it does not know x 6= 0 → (y − x =0 ∨ y − x < y). But this could be shown for the sub function byinduction and result in a dependency foetus could use [BG96].

Furthermore the call graph completion algorithm could be adopted to provetermination of parameter permuting functions like zip (see example 3.8).

If foetus has “grown older” in the described manner it could be “borninto” one of the “adult” program verfication systems or theorem provers likeALF, Isabelle, LEGO or MuTTI ;-).

References

[BG96] Jurgen Brauburger and Jurgen Giesl. Termination analysis forpartial functions. In Proceedings of the Third International StaticAnalysis Symposium (SAS’96), Aachen, Germany, Lecture Notesin Computer Science 1145, Springer-Verlag, 1996.

Page 24: foetus - Termination Checker for Simple Functional Programs

REFERENCES 24

[Gie97] Jurgen Giesl. Termination of nested and mutually recursive algo-rithms. Journal of Automated Reasoning 19: 1–29, 1997.

[NPS90] Bengt Nordstrom, Kent Petersson, and Jan M. Smith. Program-ming in Martin Lof ’s Type Theory: An Introduction. ClarendonPress, Oxford, 1990.

[Pau91] Lawrence C. Paulson. ML for the Working Programmer. Cam-bridge University Press, 1991.

[Sli96] Konrad Slind. Function definition in higher order logic. In Pro-ceedings of TPHOLs 96 (LNCS 1125), 1996.

[Sli97a] Konrad Slind. Derivation and use of induction schemes in higher-order logic. In Proceedings of TPHOLs97 (LNCS 1275), 1997.

[TTu97b] Alastair Telford and David Turner. Ensuring Streams Flow.In Michael Johnson, editor, Algebraic Methodology and SoftwareTechnology, 6th International Conference, AMAST ’97, SydneyAustralia, December 1997, volume 1349 of Lecture Notes in Com-puter Science, pages 509–523. AMAST, Springer-Verlag, Decem-ber 1997.