Copyright © 2008 David Schmidt

Chapter 3:
Functions and procedures


3.1 Pre- and post-conditions
    3.1.1 Forwards law for function call: first version
    3.1.2 How to work from specifications to code
3.2 Backwards law for function invocation
3.3 Global variables and procedures
    3.3.1 Global variables that are updated
    3.3.2 Global-variable invariants
3.4 Procedures that call procedures; procedures that call themselves
3.5 Summary


A function is a named body of commands that do significant work. When we define a function, we should summarize its knowledge-production capabilities. This is critical when we assemble a program from a collection of functions, because we connect functions together based on what they are capable of doing.


3.1 Pre- and post-conditions

A function is a ``mini-program'': it has inputs, namely the arguments assigned to its parameters, and it has outputs, namely the information returned as the function's answer. It acts a bit like a logic gate or chip, with ``input wires'' and ``output wires.''

Here is an example that shows the format we use for writing a function:

===================================================

def FtoC(f) :    # HEADER LINE : LISTS NAME AND PARAMETERS
    """F to C converts a fahrenheit temperature, f, to celsius and returns
       the answer
    """
    { pre   f is a number  # PRECONDITION : REQUIRED PROPERTIES OF ARGUMENTS
      post  answer == (5.0/9.0)*(f-32)  # POSTCONDITION : THE GENERATED KNOWLEDGE
      return answer        # VARIABLE USED TO RETURN THE RESULT
    }
    base = f - 32.0           # BODY  (no assignment to  f  allowed)
    answer = base * (5.0/9.0) # BODY  (no assignment to  f  allowed)
    return answer 

===================================================
The precondition states the situation under which the function operates correctly, and the postcondition states what the function has accomplished when it terminates.

For simplicity, we require that no assignment is made to a parameter inside the body of the function In a call-by-value language like Python and Java, this is never a handicap.

The functions's specification consists of its header line and its pre-, post-condition, and return lines; it tells us how to use the function correctly: (The comment with the informal description is also nice to have.) Here is the specification for the above function:

def FtoC(f) :
    """F to C converts a fahrenheit temperature, f, to celsius and returns
       the answer
    """
    { pre  f is a number
      post  answer == (5.0/9.0)*(f-32)
      return answer
    }
The specification tells us what we need to know to use the function correctly. The person/program who calls the function is not supposed to read the function's code to know how to use the function. This is especially crucial when you use a function from a library that was written by someone else --- you should not read the library's code to learn how to call the library's functions!

Back to the example: Since we do not allow assignments to f within the body of FtoC, we know that the value of f in the postcondition is the same as the value of f in the precondition --- this makes the postcondition meaningful.

To call the function, we supply an argument that binds (is assigned to) its parameter, and we must supply a variable that is the target for the returned answer:

===================================================

a = readInt()
{  a is an int and therefore a number }
b = FtoC(a)
{  b == (5.0/9.0)*(a-32) }
print "Fahrenheit", a, "in Celsius is", b

===================================================
To call the function, we must prove the function's precondition at the point of call. As our reward for establishing the precondition, we receive in return the postcondition, stated in terms of a and b: b == (5.0/9.0)*(a-32).

For simplicity, we require that the target variable that receives the function's answer can never appear in the argument given to the function's call, that is, a call like b = FtoC(b+2) is not allowed, because it confuses the b in the argument to FtoC with the b in the answer.

We will summarize shortly all the technical details of arguments, parameters, answers, preconditions, and postconditions.

Here is another example function; this one computes a reciprocal when asked:

===================================================

def recip(n) :
    """recip  computes the reciprocal for  n"""
    { pre   n != 0 
      post  ans == 1.0 / n
      return ans  }
    ans = 1.0 / n
    return ans

===================================================
The precondition tells us how to avoid a division-by-zero error: if the argument assigned to the parameter satisfies the precondition, then the function's answer will satisfy the postcondition.

The function should be called only in situations where we can prove that its precondition holds true. Here is an example:

===================================================

a = readInt()
if a != 0 :
    { 1. a != 0    premise  }
    b = recip(a)
    { 1. b == 1.0/a              premise  # from the function's post
      2. a != 0                  premise
      3.  a != 0  ∧  b = 1.0/a    ∧i 2 1
    }
else :
    { 1. ¬(a != 0)     premise
      2.  a == 0          algebra 1
    }
    b = 0
    { 1. b == 0                 premise
       2. a == 0                 premise
       3. a == 0  ∧  b == 0   ∧i 2 1
    }
{ 1. (a != 0  ∧  b == 1.0/a) ∨ (a == 0  ∧  b == 0)   premise }

===================================================

In contrast, we are not allowed to use recip's postcondition in this situation:

===================================================

x = readInt()
# here, we do not know if  x  is nonzero....
y = recip(x)
{  ??? }

===================================================
Since we cannot prove x != 0 as a precondition for applying the function, we cannot assert the function's postcondition after the call. Literally, we have no knowledge of what the function produces in this situation --- perhaps the function will ``crash'' (generate an exception)! (We rely on the function's specification to tell us how to use the function, and we are not supposed to dig into the function's body to guess what might happen with an ``illegal'' call.)


3.1.1 Forwards law for function call: first version

Here is a summary of what we have developed so far. Given the function,

===================================================

def f(x):
    {  pre  Qx   (that is, assertion  Q  mentions  x) 
       post  Rans,x  (that is, assertion  R  mentions  ans and x) 
       return ans  (the name of the answer variable)
    }
    BODY  # does not assign to  x
    return ans

===================================================
We say that fpre is Qx and fpost is Rans,x. Here is the schematic for a correct call of the function.
===================================================

{ ... [e/x]fpre  }
y = f(e)  # y  does not appear in  e
{ 1. [y/ans][e/x]fpost    premise
  2.[yold/y]P     premise
  ...
}

===================================================
Recall that [e/x]fpre says we substitute e for x within Qx. We can abbreviate the result as Qe.

If we correctly deduce Qe, we can call f and conclude [y/ans][e/x]fpost, that is, Ry,e, where y replaced ans and e replaced x. Since y is the target of the answer returned by the function, we must adjust P in the way we do for any assignment to y.


3.1.2 How to work from specifications to code

When you call a function that someone else wrote, you rely on the function's pre-post-conditions, that is, the specification, to tell you how the function behaves. (In many cases, you can't read the code. Consider a library, like the Microsoft Foundation Classes or .NET, where you are given the specifications but not the code to read.)

If you the person writing the function for others to use, you must supply the function's specification. You can calculate the specification using the laws we studied in the previous chapter. But better still, you should start with the specification and write the function so that it matches the specification.

The usual way to do this is to state the function's goal. Then, you study the goal and consider how to meet it. Finally, you apply the laws for assignment and conditionals to show that the coding matches the specification. Often, you begin with just the function's postcondition and you formulate an appropriate precondition after writing the code.

Here is a small example. Say you must write a function that receives two numbers as inputs and selects the maximum, or larger, of the two as the function's output. The specification of the function, max, might look like this:

===================================================

def max(x, y)
"""max  selects the larger of  x  and  y  and returns it as its answer"""
{ pre  ??? 
  post   (ans == x ∨ ans == y) ∧  (ans >= x ∧ ans >= y)
  return ...
}

===================================================
You must write the code for function max so that it meets the postcondition. Along the way, you might require some restrictions on x and y for your solution to work. These would be listed in the precondition.

The logical operators in the postcondition sometimes give us hints how to code the function. Here, we see that the function's answer will be either x or y, suggesting that the function will use assignment commands of the form, ans = x and ans = y. We also see that there is an ``or'' operator in the specification. This suggests that we will require an if-command to choose which of the two assignments to use. Although it is not an exact science, we can move from the postcondition to this possible coding:

===================================================

def max(x, y)
    { pre  ??? 
      post   (ans == x ∨ ans == y) ∧  (ans >= x ∧ ans >= y)
      return ans  }
    if  x > y :
        ans = x
    else :
        ans = y
    return ans

===================================================
Now, we use the laws for assignments and conditionals to compute whether a precondition is needed that places restrictions on x and y so that the function behaves properly. What we see here is that our coding works correctly with numbers (and with strings, too!). But it might not work correctly, say, if x or y are arrays or pointers. For this reason, it seems safe to use this precondition:
===================================================

def max(x, y)
    """max  selects the larger of  x  and  y  and returns it as its answer"""
    { pre  x and y are both numbers or both strings 
      post   (ans == x ∨ ans == y) ∧  (ans >= x ∧ ans >= y)
      return ans  }
    if  x > y :
        ans = x
    else :
        ans = y
    return ans

===================================================
Now, we have a function --- a component --- that others can insert into their programs.


3.2 Backwards law for function invocation

Here is a precise statement of how to reason backwards from a goal across a function call. First, given the specification,
def f(x):
    { pre  Qx  
      post  Rans,x 
      return ans
    }
If a call to f is meant to achieve a goal, G, we reason backwards from G to its subgoal like this:
===================================================

{ subgoal:  [e/x]fpre  ∧  ([e/x][y/ans]fpost —> G)  }
y = f(e)  # y  does not appear in argument  e
{ goal: G }

===================================================
That is, for the call, f(e), to accomplish G, it must be the case that f's postcondition implies G and that we can prove f's precondition holds true (so that we can call f).

An example: for

===================================================

def recip(n) :
   { pre  n != 0 
     post  ans == 1.0 / n 
     return ans
   }

===================================================
and the program
===================================================

a = readInt()
{  subgoal: ??? }
b = recip(a)
{  goal: b < 1.0/3 }

===================================================
we compute that the subgoal is a != 0 ∧ (b = 1.0/a —> b < 1.0/3). This simplifies to a != 0 ∧ (1.0/a < 1.0/3)), which simplifies to a != 0 ∧ a > 3, which simplifies to a > 3. This tells us what assert or if command to add to our program:
===================================================

a = readInt()
assert a > 3
{  subgoal: a > 3 }
b = recip(a)
{  goal: b < 1.0/3 }

===================================================
That is, if we expect the computed reciprocal to be less than one-third, then we must supply an input int that is 4 or more.


3.3 Global variables and procedures

The term, ``function,'' suggests that the named body of commands will accept one or more arguments and return an answer. But we can have functions that return no answers even receive no arguments, because they can read and update the values of non-local, global variables. We call a function that updates a global variable, a procedure.

Say that a variable, v, is ``global'' to a function f if v exists prior to a call to f. (More precisely stated, v's cell does not live in f's namespace/activation-record.) For example, pi is global to circumference here:

===================================================

pi = 3.14159

def circumference(diameter) :
    answer = pi * diameter
    return answer

  . . .
r = readFloat("Type radius of a circle: ")
c = circumference(2 * r)          # compute circumference of circle
area = r * r * pi                 # compute circle's area


===================================================
A global variable ``lives'' before a function is called and lives after the function finishes. In the above example, pi is defined before the function is called, and it exists after the function finishes. In contrast, pi is local here:
===================================================

def circ(d) :
    pi = 3.14159   # pi is created new each time  circ  is called
    answer = pi * diameter
    return answer

  . . .
r = readFloat("Type radius of a circle: ")
c = circumference(2 * r) 
# pi  does not exist here; it cannot be referenced:
area = r * r * pi   # ???!

===================================================

A global variable that is read (but not updated) by a function body can be safely used in the function's pre- and postconditions; it acts just like an extra parameter to the function:

===================================================

pi = 3.14159

def circumference(diameter) :
    { pre  diameter >= 0  ∧  pi > 3
      post  answer = pi * diameter 
      return answer }
    answer = pi * diameter
    return answer

===================================================
We must restrict all calls to the function so that the call does not use the global variable as the target of the function's answer. That is, c = circumference(5) is ok, and so is c = circumference(pi), but pi = circumference(5) is not.


3.3.1 Global variables that are updated

We use the name, procedure, for a function that updates a global variable. A global variable that is updated by a function acts like an extra answer variable for the function.

In the Python language, every global variable that is updated by a function must be listed in the global line that immediately follows the function's header line. This is a good practice that we will follow.

Here is a first, simple example:

===================================================

pi = 3.14159
c = 0

def circumference(diameter) :
    { pre  diameter >= 0  ∧  pi > 3
      post  c = pi * diameter   
    }
    global c  # will be updated by this procedure
    c = pi * diameter

 . . .
d = readFloat("Type diameter of a circle: ")
assert d >= 0
{ 1. d >= 0                   premise
  2. pi == 3.14159            premise
  3. pi > 3                   algebra 2
  4. d >= 0  ∧ pi > 3        ∧i 1 3  
  # we proved the precondition for  circumference
}
circumference(d)
{ 1.  c == pi * d       premise  }  # we get its postcondition
print c

===================================================
The answer computed by procedure circumference is deposited in global variable, c. For this reason, the procedure's postcondition is stated in terms of diameter and and c, and the postcondition applies after the procedure is called.

Here is a precise statement of the law we use for procedures:

===================================================

g = ...   # the global variable

def f(x):
    { pre  Qx,g     (that is, assertion  Q  mentions  x and g) 
      post  Rans,x,g    (that is, R  mentions  ans, x, and g) 
      return ans
    }
    global g          # notes that  g  can be updated by  f
    BODY              # does not assign to  x  but may assign to  g
    return ans        # this line is optional

===================================================
To invoke the function, we deduce the following:
===================================================

{ ... [e/x]fpre  }
y = f(e)   # y and g  do not appear in  e,  and  y and g  are distinct
{ 1. [y/ans][e/x]fpost   premise
  2. [yold/y][gold/g]P   premise
  ...
}

===================================================
Since global variable g acts as a second answer variable, g cannot appear in argument e nor can it be the same as y.


3.3.2 Global-variable invariants

A global variable is often shared by more than one procedure, and the procedures must cooperate and keep the global variable in proper shape for shared use. For this reason, a global variable often has attached to it a global-variable invariant that asserts a property of the variable's value that must be preserved by every procedure that updates the variable. (In an object-oriented language, when a global variable and its functions are defined within a class, the global-variable invariant is called the class invariant.)

Whenever a module or class holds a shared data structure, there should be a global invariant that states the critical properties of the structure that must be maintained. The procedures that update the structure pledge to preserve the global invariant.

This is the technique used in modular and object-oriented programming, where a data structure, its global invariant, and the data structure's update functions are placed together. Since only the maintenance functions update the global variable and preserve the invariant, the rest of the program can always rely on the global invariant to hold true. Without this convention, it is impossible to conduct proper component-based programming.

Here is a tiny example that makes this point: It is a module/class that models a bank account with a global variable and two maintenance functions: The variable's value should always be kept at a nonnegative value:

===================================================

# A bank account and its maintenance functions.

# The global variable, the money in a bank account:
account = 100
{ 1.  account == 100     premise
   2.  account >= 0       algebra 1
}
# the global invariant:
{ globalinv  account >= 0  }  # this property is currently true, and
                                #  we want the functions to preserve it

def deposit(howmuch) :
    """deposit adds  howmuch  to  account"""
    { pre  howmuch >= 0    
      post  True }
    global account
    { 1.  account >= 0    premise # the globalinv holds on entry
      2.  howmuch >= 0    premise  # the function's precondition
    }
    account = account + howmuch
    { 1. account = accountold + howmuch    premise
      2. accountold >= 0  ∧  howmuch >= 0    premise 
      3. account >= 0                         algebra 1 2
    }
    # The global invariant is preserved at the procedure's exit.

def withdraw(howmuch) :
    """withdraw  removes  howmuch  from  account"""
    { pre  howmuch >= 0      
      post  True } 
    global account
     { 1.  account >= 0    premise
       2.  howmuch >= 0    premise
    }
    if  howmuch <= account :
        { 1.  howmuch <= account    premise }
        account = account - howmuch
        { 1. account = accountold - howmuch    premise
          2.  howmuch <= accountold            premise
          3.  account >= 0                     algebra 1 2
        }
    else :
        { 1. ¬(howmuch <= account)       premise
          2.  account >= 0                  premise
        }
        pass
        { 1. account >= 0                  pass }
    { 1. (account >= 0) ∨ (account >= 0)   premise
      2.  account >= 0)                     ∨e 1
    }
    # The global invariant is preserved at the procedure's exit.



# Later in the program, wherever it's needed, we can assert:
{ ...
   k.  account >= 0        globalinv
   ...  }
# but we CANNOT do any assignments to  account  within the program;
# we call the functions,  deposit and withdraw,  to do the assignments.

===================================================
The two functions, deposit and withdraw, pledge to always keep account's value nonnegative. Assuming that no other program commands update the account, the proofs of the two functions suffice to ensure that account's global invariant holds always.

In the previous example, there is a second, critical invariant: the account balance correctly totals all the transactions made to the bank account, that is, the balance is the correct number. If we add a log to the component, we can state this correctness property as an invariant, too.

We add an array (list), named log, and append to it all the amounts deposited and withdrawn. For example, if an account starts with a deposit of 100 followed by a withdrawal of 50 and a deposit of 10, we would have

log = [ +100, -50, +10 ]
and it must be that variable account == 60. The relationship is stated as this invariant:
account == summation i: 0..len(log)-1,  log[i]
that is, account == log[0] + log[1] + ... + log[len(log)-1], where len(log) is the length of array/list, log.

Here is the revised coding of the component:

===================================================

### A MODULE/CLASS THAT MODELS A BANK ACCOUNT ###############

# The global variable, the money in the account:
account = 100       # we'll start at 1d0....
{ globalinv  account >= 0  } 

# A real account maintains a log of all transactions:
log = [+100]    # log  is an array (list) that grows.  Here, it has one entry.
{ globalinv   account == summation i:0..log(len-1), log[i] }

# We can use  def  clauses, explained in later in this Chapter, and arrays,
# explained in Chapter 6, to state and prove this property.

def deposit(howmuch) :
    """deposit adds  howmuch  to  account"""
    { pre  howmuch >= 0 
      post  True }
    global account, log
    { 1.  account >= 0    premise # the globalinv holds on entry
      2.  howmuch >= 0    premise  # the function's precondition
      3.  account >= 0 and howmuch >= 0  andi 1 2
    }
    account = account + howmuch
    { 1. account == accountold + howmuch    premise
      2. accountold >= 0  and  howmuch >= 0    premise 
      3. account >= 0                         algebra 1 2
    }
    log.append(0+howmuch)   # record the transaction in the log
    # We must prove the global invariants are preserved at the exit.

def withdraw(howmuch) :
    """withdraw  removes  howmuch  from  account"""
    { pre  howmuch >= 0 
        post  True } 
    global account, log
    # the same premises hold here like in  deposit
    if  howmuch <= account :
        account = account - howmuch
        { 1. account == accountold - howmuch    premise
          2.  howmuch <= accountold            premise
          3.  account >= 0                     algebra 1 2
        }
        log.append(0-howmuch)  # record the transaction in the log
    else :
        { 1. not(howmuch <= account)     premise
          2.  account >= 0               premise  # the invariant
        }
        pass
        { 1. account >= 0                premise }
    # END IF
    { 1. account >= 0                     premise }
    # We reprove the global invariants here.

===================================================

There are many other examples of modules/classes that use global invariants:

These examples suggest that a key job when doing modular and object-oriented programming is writing functions that preserve global invariants. For this reason, when you design a program that works with data structures, define the data structures and their invariants before you write the functions that maintain the data structures.


3.4 Procedures that call procedures; procedures that call themselves

We already know how to make a procedure call another procedure:
===================================================

def recip(n) :
    { pre   n != 0
      post  ans == 1.0 / n 
      return ans
    }
    ans = 1.0 / n
    return ans

def main()
    { pre  True 
      post  (x !=0) —>  (y == 1.0 / x) 
    }
    x = readFloat("Type a number: ")
    if x != 0 :
       y = recip(x)
       print y
    else :
       print "can't compute reciprocal"

===================================================
It is even more interesting that a procedure can call itself, and we can reason about this in the same way that we use for any other procedure call. Here is a detailed example.

For integer n > 0, the factorial of n, written n! is defined as 1*2*3*...up to...*n. It is the tradition to define 0! = 1, but factorials for negative integers do not exist.

Factorial lists number of permutations, e.g., fact(3) = 6 notes there six permutations (combinations) for arranging three items, a, b, and c:

abc
bac
bca
acb
cab
cba
There is a clever recursive definition of factorial, which tersely states the pattern of multiplications from 1 up to m:
0! == 1
n! == (n-1)! * n,   for  n > 0
For example, we calculate 4! like this:
===================================================

4! == 3! * 4
   where 3! == 2! * 3
            where 2! == 1! * 2
                     where 1! == 0! * 1
                              where 0! == 1
So...
   0! == 1
   1! == 0! * 1 = 1
   2! == 1! * 2 = 2
   3! == 2! * 3 = 6
and finally,
   4! == 3! * 4 = 24

===================================================
The process of counting downwards from 4! to 3! to 2! to 1! to 0! and assembling the answer as 1 * 1 * 2 * 3 * 4 can be programmed as a function that repeatedly calls itself for the answers to 3!, 2!, 1!, etc.:
===================================================

def fact(n) :          # returns  n!
    { pre  n >= 0  
       post  (to come)
       return  ans  
    }
    if n == 0 :
        ans = 1        # this computes  0! = 1
    else :
        a = fact(n-1)  # this computes  (n-1)!
        ans = a * n    # this computes  n! = (n-1)! * n
return ans

===================================================
The easiest way to understand the computation of, say, fact(3), is to draw out the function calls, making a new copy of the called function each time the function is restarted, like this:
===================================================

fact(3) =>  n = 3
            if n == 0 :
              ans = 1
            else :
              a = fact(n-1)
              ans = a * n
            return ans

===================================================
Notice how the binding of argument 3 to parameter n is enacted with the assignment, n = 3. (This is how it is implemented within a computer, too.) The code for fact(3) itself calls (activates a fresh copy of) fact with argument 2:
===================================================

fact(3) => n = 3
           a = fact(2) =>  n = 2
                           if n == 0 :
           ans = a * n        ans = 1
           return ans      else :
                              a = fact(n-1)
                              ans = a * n
                           return ans

===================================================
This generates another call (fresh copy of) fact:
===================================================

fact(3) => n = 3
           a = fact(2) =>  n = 2
           ans = a * n     a = fact(1) => n = 1
                                          if n == 0 :
           return ans      ans = a * 2       ans = 1
                           return ans     else :
                                             a = fact(n-1)
                                             ans = a * n
                                          return ans

===================================================
This goes to
===================================================

fact(3) => n = 3
           a = fact(2) =>  n = 2
           ans = a * n     a = fact(1) => n = 1
           return ans      ans = a * n    a = fact(0) =>  n = 0
                                                          if n == 0 :
                           return ans     ans = a * n       ans = 1
                                          return ans     else :
                                                            . . .
                                                         return ans

===================================================
We see a sequence, or ``stack,'' of activations of fact, one per call. Within a computer, an activation-record stack is implemented to remember the sequence of activations.

The call to fact(0) returns an answer --- 1:

===================================================

fact(3) => n = 3
           a = fact(2) =>  n = 2
           ans = a * n     a = fact(1) => n = 1
           return ans      ans = a * n    a = fact(0) => 
                           return ans     ans = a * n    return 1
                                          return ans     

===================================================
This makes the most recent activation (copy) of fact disappear, and the returned answer is assigned to ans in the previous call,
===================================================

fact(3) => n = 3
           a = fact(2) =>  n = 2
           ans = a * 3     a = fact(1) => n = 1
           return ans      ans = a * 2    a = 1
                           return ans     ans = a * n 
                                          return ans     

===================================================
allowing the previous call to return its answer to its caller:
===================================================

fact(3) => n = 3
           a = fact(2) =>  n = 2
           ans = a * 3     a = fact(1) => 
           return ans      ans = a * 2    return 1
                           return ans     

===================================================
You see the pattern --- the calls from fact(3) to fact(2) to fact(1) to fact(0) are finishing in reverse order and are returning the partial answers, fact(0) = 1, fact(1) = 1, fact(2) = 2, and so on:
===================================================

fact(3) => n = 3
           a = fact(2) =>  n = 2
           ans = a * n     a = 1
           return ans      ans = a * n
                           return ans     

===================================================
and then
===================================================

fact(3) => n = 3
           a = fact(2) =>  
           ans = a * n     return 2
           return ans 

===================================================
and
===================================================

fact(3) => n = 3
           a = 2
           ans = a * n
           return ans      

===================================================
and finally
===================================================

fact(3) => return 6

===================================================
Within the computer, the code for fact is not copied at each call --- instead, a new namespace (activation record) is generated for each call to fact. This saves storage and computes the same result as the ``copy rule semantics'' showed us above.

Every function has its own pre- and post- conditions. So, if a function calls itself, it can use its own pre- and postconditions to deduce the properties of the answer computed by the self-call. This is remarkable and exactly correct.

Here is the desired specification of fact:

def fact(n)
    { pre  n >= 0 
      post  ans == n! 
      return ans
    }
When fact calls itself, we use the above pre- and postconditions to deduce what happens. In the process, we deduce that the completed coding of fact possesses exactly these same pre- and postconditions!

Recall again the ``official definition'' of n!:

0! == 1
n! == (n-1)! * n,  for  n > 0
Here is the deduction that fac meets its stated pre- and postconditions:
===================================================

def fact(n) :
    { pre  n >= 0
      post  ans == mult(n)
      return ans  
    }
    if n == 0 :
        ans = 1
        { 1. ans == 1       premise
          2. n == 0          premise
          3. ans == 0!       definition of  0! == 1
          4. ans == n!       subst 2 3
        }
    else :
        { 1. ¬(n == 0)     premise
           2. n  > 0         premise
          3. n - 1 >= 0      algebra 1 2  # this proves  prefac
        }
        sub = fact(n-1)
        { 1.  sub == (n-1)!    premise }  # postfac 
        ans = sub * n
        { 1.  ans == sub * n          premise
          2.  sub ==  (n-1)!           premise
          3.  ans ==  (n-1)! * n       subst 2 1
          4.  ans ==  n!               definition of  n! == (n-1)! * n
        }
    { 1.  ans == n!   premise }
    }
    return ans

===================================================
We did it! By guessing a useful pre- and postcondition, we proved that the coding of fact that calls itself establishes the pre- and postconditions.

Here is a sample call of the end result:

===================================================

{ 200 >= 0, that is  [200/n]prefact }
x = fact(200)
{ [x/ans][200/n]postfact,  that is,  x == 200! }

===================================================
In the next chapter, we will see a strong connection between a function's self-call and a loop --- in both cases, the construct reuses its very own ``pre-post-condition'' when repeating itself.


3.5 Summary

We used a variety of laws for function definitions and calls.