The -calculus was intended as a kind of Mathematical
Logic, and we shall use it as such to construct proofs about
programs in our language within our language. Essentially the idea
is that since the
-calculus provides the definition of
our language, we can require that any implementation obeys the laws of the
calculus, together with additional laws which characterise the behaviour of
the built-in System capabilities.
For example, suppose we want to prove that a sorting function that we
have written, say merge_sort, is correct. Then since we are
operating within the functional paradigm, merge_sort should be
exactly equivalent to an expression of the -calculus.
What does it mean to say that merge_sort is correct? There are two
criteria:
(merge_sort l)is sorted. How do we render this English requirement into a formalism? Well it's fairly easy to write a function sorted that determines if a list is sorted - a list is sorted if it is empty, or contains one element, or if the first two elements are in order and tail of the list (everything but the first element) is sorted. So our requirement can be expressed as
Proving that programs are correct at the level of detail to which we aspire is an undecidable problem, for our approach is in effect to show that two different programs always produce the same result when given the same data, which is known to be undecidable if taken over a wide enough class of programs.
However, there is nothing in the undecidability result which says that it is impossible to prove that a particular program is correct. So, it is reasonable to attack the problem of proof with a combination of human ingenuity controlling a collection of inference rules provided as functions in the language itself.[Theoretical Foundations of Programming Languages and their Practical Realisation.
Practically, this can be realised by using an abstract data-type Theorem with the property that the only ways a language-user can get hold of a theorem are guaranteed to ensure the validity of the theorem.
A theorem has two essential components, its premises and its conclusion. Our theorems will also carry a history with them. A theorem prints as
premises |- conclusionFor example, we might have
Integer.? x, Integer.?y |- x+y = y+xWhich states that it doesn't matter whether you add x to y or y to x. If there are no premises, then we write simply
|- conclusionFor example we require:
|- x = x
The meaning of theorems is a bit tricky to define. Essentially,
the premises are a sequence of things that look like terms of the
-calculus, while the conclusion is one of these
term-like things. Notionally, the premises and conclusion "evaluate" to one
of the values "true" or "false", and we require of our theorems that if
all the premises "evaluate" to true, so does the conclusion.
The tricky bit is that such evaluation is not constructive. This has to be so because we are interested in proving theorems about equality of functions. We'd like, for example to be able to prove that
|- \x. x+3 = \x. 3+x
The conclusion \x. x+3 = \x. 3+x looks like a term of the -calculus, but we can't evaluate it in the ordinary way
we'd evaluate a program. Instead, we have to employ a mathematical concept
of evaluation. In this particular case, the meaning of the equality can be
taken to be that if each of these
-abstractions is
given any of a countably infinite universe of objects of values,
the results are computably identical.
Theorems provid arbitration between users and implementors.
(System 'Theorem' 'Axiom' 'Integers' 'C_plus')will evaluate to the theorem
(Integer.? x), (Integer.? y) |- ((Integer. +) x y) = ((Integer. +) y x)which states that the built-in addition operation is commutative over the integers.
(BETA '((\x. 5+x) 20)')evaluates to
|- '((\x. 5+x) 20)' = 5+20The BETA function thus creates a theorem which says that an expression is unchanged in "meaning" by
Incidentally, the functional nature of our work is built-in to rules-of-inference. For the reflexive law of equality is one of the primitive rules-of-inference: (REFL 'fred x') evaluates to the theorem
|- (fred x) = (fred x)But it is simply not true that, in an imperative language such as C, that fred(x) = fred(x) for a call of an arbitrary function fred - fred might side-effect a global variable, so evaluations of fred in apparently identical circumstances can yield different results.
Definition double = \x. 2*x End;As a result, the system creates an object with the status of a theorem:
|- double = \x. 2*x
However we have no control over the scope of such a theorem - we certainly don't want to get the "double" confused with any one we might want to define locally. This is an issue currently unresolved in the design of POP2000 - it would not however be problematic to ensure in some way that local and global variables have distinct names, when used in theorems derived from definitions.