TFL User Manual

This document describes the TFL system and how one can use it. TFL, which stands for Total Functional Language, is a verification system for higher-order polymorphic functional programs, much like those of ML or Haskell, with one special property: each program must terminate. TFL models programs with the native functions of a higher order logic; this has the advantage that the built-in reasoning machinery for functions can be directly applied to reasoning about programs, i.e., no special verification theory has to be built.

Using an instantiation of TFL

In order use use an instantiation of TFL, one must know the language allowed to express recursive definitions in. There are two components of this: how to express the recursion equations, and how to express termination.

Recursion Equations

Function definitions are very much like those of ML, with the restriction that patterns must be exhaustive, and must be presented as a single argument. This means that equations like the following are allowed:
    f x = if (x=0) then 1 else x * f(x-1)

    ack (0,n) =  n+1
    ack (Suc m,0) = ack (m, 1)
    ack (Suc m, Suc n) = ack (m, ack (Suc m, n))
but that the following are not (the first two have overlapping patterns, and the second function has two arguments):
    g (0,x) = 1
    g (y,0) = 2

    f 0 = 1
    f y = y * f (x-1)

                A 0 n = n+1
          A (Suc m) 0 = A m 1
    A (Suc m) (Suc n) = A m (A (Suc m) n)

Expressing Termination

Termination expressions are built out of wellfounded relations. A wellfounded relation is one that "allows no infinite decreasing chains". Consider an arbitrary invocation f x of a function f, leading to recursive calls f y_1,...,f y_m. If we can place (y_1,x), ..., (y_m,x) in a wellfounded relation, then f terminates. Thus our job involves finding wellfounded relations and proving termination, i.e., that recursive calls belong in the selected wellfounded relation.

Fortunately, it is usually simple to build the right wellfounded relation. The basic wellfounded relation for a datatype is the immediate subterm relation. This is wellfounded since one can only finitely often take the immediate subterm of a datatype element before getting to a term with no subterms. For example, in the natural numbers, the predecessor relation is the immediate subterm relation. Using the immediate subterm relation for a datatype gives primitive recursive definitions, but we can make much more powerful termination expressions by use of the following combining forms: transitive closure, inverse image, and lexicographic combination (there are others available, but these are the most useful). Following are the definitions:

   inv_image R f = \x y. R (f x) (f y))

   lexico R1 R2 = \(s,t) (u,v). R1 s u \/ (s = u) /\ R2 t v)

   measure = inv_image (<)
Now we give some example termination expressions and describe what functions would terminate under them. More complicated examples, like Ackermann's function, require termination expressions involving lexicographic combinations, but the reader can see these in the examples.

TFL Entrypoints

In this section we discuss the entrypoints available in an instantiation of TFL. Using these entrypoints, one can quickly build easy-to-use facilities for defining recursive functions and reasoning with recursion induction principles derived from such definitions.

The structures Rules, Thms, Thry, and USyntax represent the basis on which TFL is built. They provide such things as common syntactic manipulations, specific theorems and inference rules, and access to the underlying theory mechanism. The ordinary user should have no great need to look at the implementations of these structures.

TFL also uses a collection of types:


   val mk_functional : Thry -> Preterm ->
                       {functional:Preterm, patterns:Preterm list}
mk_functional takes a theory and a conjunction of recursion equations and returns the functional corresponding to the equations and also the patterns used in the equations. The main component in this operation is an ML-style pattern-matching translation. For example, in HOL, with recursion equations
    (Fact 0 = 1) /\
    (Fact (Suc x) = Fact x * Suc x)
the output of mk_functional is
    {functional = `\Fact a. num_case 1 (\v. Fact v * Suc v) a`,
     patterns = [`0`,`Suc x`]}.
Note that the function being represented (Fact in this case) should be a variable in the recursion equations.
   val prim_wfrec_definition : Thry 
                                -> {R:Preterm, functional:Preterm}
                                -> {def:Thm, corollary:Thm, theory:Thry}
prim_wfrec_definition is the main underlying definition mechanism. It takes a relation R and a functional which looks like \f x. N, the latter presumably derived from mk_functional, and makes the definition def
     f = WFREC R (\f x. N).
It then instantiates and simplifies the wellfounded recursion theorem to get corollary. For our example, we get, using (\m n. n = Suc m) for R:
  {def= |- Fact =
            WFREC (\m n. n = Suc m) 
                  (\Fact a. num_case 1 (\v. Fact v * Suc v) a),
  corollary= |- WF (\m n. n = Suc m) ==>
                   !x. Fact x = (\Fact a. num_case 1 (\v. Fact v * Suc v) a)
                                (Fact % (\m n. n = Suc m),x)
                                x}.

   val gen_wfrec_definition : Thry 
                               -> {R:Preterm, eqs:Preterm}
                               -> {theory:Thry, rules:Thm, 
                                   TCs:Preterm list list}
gen_wfrec_definition acts essentially as a pre- and post-processor for prim_wfrec_definition. It takes R as before, and takes eqs, a conjunction of recursion equations. It then calls mk_functional to translate the recursion equations to a functional, and then invokes prim_wfrec_definition on the result to make the definition. After that, there is a phase of termination condition extraction: this traverses the definition and brings out formulae that, when proven, allow the definition to be used freely. For example, an invocation with R = (\m n. n = Suc m) and eqs = fact x = if (x = 0) then 1 else x * fact(x-1) gives the result (momentarily ignoring the returned theory component):
    {rules = [WF (\m n. n = SUC m)]
             |- (!x. ~(x = 0) ==> (\m n. n = SUC m) (x - 1) x) ==>
                (fact x = ((x = 0) => 1 | (x * fact (x - 1)))),
     TCs = [[`!x. ~(x = 0) ==> (\m n. n = SUC m) (x - 1) x`]]}
Note well that even if these are not proven, the definition can still be used. This is in contrast to some other approaches, where termination must be proved before anything can be doen with the function.
   val wfrec_eqns : Thry -> Preterm -> Preterm * Preterm 
                                       * (Thm * Preterm list) list

   val lazyR_def : Thry -> Preterm -> {theory: Thry, eqns:Thm, 
                                       TCs:Preterm list list}
wfrec_eqns is used to implement lazyR_def, and is not of much interest.
   val mk_induction : Thry -> Thm -> Preterm list list -> Thm

   val postprocess: {WFtac:Tactic, terminator:Tactic, simplifier:Term -> Thm}
                    -> Thry 
                      -> {rules:Thm, induction:Thm, TCs:Preterm list list}
                          -> {rules:Thm, induction:Thm, TCs:Thm list}

   val new_context : Thm -> unit
   val context_congs : unit -> Thm list

Instantiating TFL

(settable parameters,what the rewriter has to do)
Table of Contents
Konrad Slind, slind@informatik.tu-muenchen.de