Substitution


In this seminar we will look at the theory of functional programming languages, and consider the practical aspects of implementing them. The primary motivation of writing functionally is to develop programs whose behaviour can easily be analysed mathematically, and which in any case is easy to predict and understand.

Generally the application of computers to the engineering design is somewhat ahead of their application to software design. In particular it is common for engineers to use a tool such as Mathematica or Maple to assist in performing an analysis of the behaviour of a system before it is implemented. In this seminar we'd like to explore how functional languages can be used to address this gap by supporting detailed mathematical analysis of program behaviour.

In implementing programs it is common enough to employ what might be called "macroscopic" tools to analyse the behaviour of a program at a rather coarse level of detail. The most widely used such tool is the concept of data type which is built into most programming languages. Type-analysis will tell you that x+"fred" is an illegal expression (in most languages) because you can't add a number to a string. It won't tell you that you have made an error if you have written x+1 when you meant x-1.

On the other hand, Mathematica can tell you precisely that - for example if you want a solution of the equation x2+2x+1, Mathematica can determine that x+1 is not a solution of that equation (or of course it can solve the equation for you).

But computer programs in general are opaque to the sort of analysis that Mathematica and Maple can perform. Before we can even begin to prove that a program is correct, we have to characterise the behaviour of the programming language in which the program is written. Functional programming languages are characterised in a particularly simple way: every component of a program is treated as a mathematical function whose meaning follows the simple rule that within a given context identical expressions have identical values.

To give an example of what we mean, in a pure functional language a mathematical identity like:

         fred(x) + fred(x) = 2*fred(x)
should hold. This is not necessarily the case for a non-functional language, for example in Pascal or C fred might be a procedure which had a side-effect, so that calling it twice has a different effect from calling it once. For example fred might contain the assignment g:=g+1 where g is a global variable. Thus the value of fred(x) varies with the number of times it has been evaluated.

In the seminar we will:

The Lambda Calculus Provides our Basic Formalism

If you ask a mathematician "what, fundamentally, is a function", he is likely to talk about a set of (argument,value) pairs. He may well characterise the relationship between arguments and values in some way that he regards as adequate, without any guarantee that there is any way, given an argument, of computing the corresponding value. For example, mathematicians can (and do) speak of a function f defined on the real numbers for which f(x)=0 when x is rational, and f(x)=1 when x is irrational. But any attempt to implement such a function computationally would require us to solve undecidable problems. [Note that, while some programming languages refer to floating-point numbers as "real" numbers, they are in fact only crude approximations, quite inadequate for representing this particular function f.]

This kind of approach is fine for mathematicians, who are usually not too concerned about whether they can actually construct the entities of which they speak. As computer scientists we need some mathematical way of characterising functions which, as a formalism, guides us exactly into something we can realise as a computation.

The generally agreed solution to this requirement of constructability of functions is to use as our basic formalism the -calculus, invented by Church, the American logician, circa 1941. It was developed as a branch of mathematics, rather than as a programming language (antedating all programming languages), and was thus defined essentially as marks on a piece of paper. However it is very easily represented in a computer. The -calculus is capable of expressing any computation, like a Turing machine, but, unlike a Turing machine, it can be made the basis of elegant and powerful computer languages.

One of the conceptual problems that beginners in formal logic, and the -calculus is a formal logic, face is the `bootstrapping conundrum', namely that we are trying to build a very formal system using our less formal ideas of ordinary mathematics, which were, in our teens, build using the even less formal ideas expressible in natural language. There is probably little that can be said except to point out that there is a computational analogy - if for example we want to build a super-secure computer system, it will depend on using less-secure tools, and ultimately on hand-crafted machine code which is not secure at all. Nevertheless, by careful checking and cross checking, and incorporating tests for consistency, we can build systems in which we trust. -calculus

Expressions in the -calculus

Most programming languages have a quite complex syntax that can take days or even weeks to learn. The -calculus has an exceedingly simple syntax. The syntactic definition given below is intended to emphasise the role of the -calculus as a basis for programming languages. We use a bold font for non-terminal symbols of the syntax (for example E1).

The -calculus consists of a set of expressions.

Basically, that's it! We can begin to give some meaning to this syntax, providing some explanation of what a given expression denotes. Doing this systematically will take quite a bit of work, but roughly we can say:

As defined above, complex expressions in the -calculus would have a great many parentheses. To avoid the confusion to the human reader that all these parentheses create, it is conventional to write an expression of the form

as (E1 E2 E3... En) and to write ( v.(E1E2...En)) as ( v.E1 E2...En), or even to omit the outer parentheses if that can be done without ambiguity. The convention about the syntactic scope of a -abstraction is that it extends as far to the right as possible. Thus means and not

Note that application is not an associative operation, so that E1 (E2 E3) is not the same as
(E1 E2) E3. Note that in the -calculus every function takes exactly one argument, which is why the form of the abstraction is v.E, with just one variable v. However, provided we remember this fact, it is syntactically convenient to write

to mean and similarly for more variables.

LISP and the -calculus

A word of warning is needed here to readers who are familiar with the LISP language, or any derivative such as Scheme: It would appear that LISP was inspired by the -calculus, but is certainly not an exact implementation of it, despite the signifcant syntactic similarity. Thus the LISP S-expression (u v w) does not mean ``apply u to v and then apply the result to w, as it would in the -calculus.

However more modern functional languages which on the surface appear more remote from the -calculus, are in fact much closer. For example the SML expression:

    fn x=> x*x;
is quite a faithful representation of the -calculus expression x. (* x x).

-reduction

Above we stated that the abstraction x.E could be understood as meaning "that function which maps the variable v to the expression E". We can give a practical meaning to this statement by defining exactly what it means to apply a -expression to an argument. Consider the application (( x. (+ 10 x)) 12). We've said that x. (+ 10 x) means "that function which maps x to (+ 10 x). So, if we give it an argument 12 we should expect to get (+ 10 13) as the result. That is to say, we have substituted 12 for x in the body of the expression.

To make this precise we have to define what we mean by substitution. We need a little notation first. We'll say that

means "the expression E1 with E2 substituted for x". Roughly what this means is "go through the expression E1 and replace every occurrence of x by E2". However substitution is not quite as simple as that. For example the variable x might be used (or as we say rebound in a -abstraction occurring inside E1; this isn't the only complication. We'll defer the full definition of substitution until later.

The process of converting the application of a -abstraction to an argument by substituting the argument for the variable of the abstraction in the body of the abstraction is called "-reduction". We write

For example, ( x. (+ 10 x)) (+ 2 3)   (+ 10 (+ 2 3))

There is no requirement that the argument of the application should be "worked out" before a -reduction is done. So, in the above example, we don't work-out that (+ 2 3) is 5 before doing the -reduction - indeed such "working out" is treated by another mechanism, -reduction, discussed below.

The Pure Lambda Calculus

If we take the -calculus as specified above and throw out the constants, we obtain the pure -calculus. It turns out that the pure calculus is theoretically adequate to specify any computation in much the same sense that a Turing machine is adequate. This is achieved -reduction successively to transform a computation expressed as an expression of the -calculus into another expression which is the result of the computation.

Mathematicians, who as a class have swallowed Occam's razor hook line and sinker, may be satisfied with the pure calculus. [The author trusts that his mixed metaphors will not give his readers indigestion] However, as a model for practical computation, it is inadequate. In the pure calculus, for example, one can represent the number 3 as a -abstraction f x. f(f(f(x))). This is similar to the use of the Peano postulates in your Discrete Mathematics course to do arithmetic. You'll remember that 2+1 = 3 is rendered as:

Neither is much help if we want to calculate our income tax! However, since the pure calculus is nice and simple, we'll make some use of it in our theoretical discussions.

Functional languages and the -calculus

The -calculus serves as a mathematical specification for the design of functional programming languages. However, design of an actual language gives rise to complications, syntactic and semantic. The syntactic complications serve one main purpose, namely to assist human comprehension of a program written in a language. We have already seen two such concessions to human frailty in the suppression of unnecessary parentheses and 's.

Another feature of programming languages that is something of a historical accident is that a program is usually composed using only ASCII characters.

Within the constraint of using ASCII, most functional languages make further concessions to perceived human need by adopting syntactic conventions that approximate to normal mathematical usage. For example most functional languages will allow the user to render:

    (+ u (* v w))
as
    u + v*w

Provided that it is a translation of such syntax back to the is rigorously defined, there is no objection to its use.

There are a number of conventions for rendering -abstractions in ASCII. The one we will prefer is that used in Haskell. The abstraction v.E is rendered in ASCII as \v.E where E is the rendering of E. For example our old friend x. (+ 10 x) is rendered as

      \x. 10+x

Bound and Free variables

The concepts treated in this section are immediately necessary for the purpose of defining precisely the substitution operation required for reduction. However they will recur continually in our study of programming languages. Thus in the Modula-2 fragment:

PROCEDURE f(i:INTEGER):INTEGER;
BEGIN RETURN(i+n)
END f

The variable n occurs free in f, and the question of how such free variables are handled is an important implementation issue.

Binding an occurence of a variable

Let E be an expression of the -calculus, and let v be a variable. We say that an occurrence of v in E is bound if it is inside a sub-expression of E of the form v.E2. An occurrence is said to be free otherwise. Thus v occurs bound in v. x v and y v. v but it occurs free in x. v x.

Note that we are speaking of an occurrence of a variable as being bound - a variable can occur both bound and free in a given expression. For example, in v v. v, the first occurrence of v is free, and the last is bound.

We can define a function FV on an expression by: FV(v) = \{v\} for a variable v\\ FV(c) = \{\} for a constant c\\ FV(E1E2) = FV(E1)\cup FV(E2) \\ FV( v .E) = FV(E) - \{v\}\\ An expression E is said to be closed if it has no free variables, i.e. FV(E) = \{\}.

When are two expressions in -calculus `equal'?

In secondary school we learned that expressions can be ``worked out'' or evaluated, by substitution and arithmetic. Thus x+2, with x=4, evaluates to 6. - and \delta- reduction play the `working out' role for -calculus. However we also learned ways of determining that two expressions were identically equal. Thus x+2 and 2+x are identically equal in school algebra. In this section we want to examine the question `can we say whether two expressions in the -calculus are equal?'.

calculus expressions denote functions

In school algebra the symbols stand for numbers. In the -calculus they stand for, or as we say denote, functions (and other entities as well if we have added constants to make our -calculus impure). It turns out to be non-trivial to develop a theory of denotation for the -calculus - it took mathematicians nearly 30 years to come up with a satisfactory one, which we will not discuss here. However we will use some informal ideas of denotation in our discussion.

For example we may say that x. +\; x\; 3 denotes the function `add 3', which is commonly regarded formally as the infinite set \{... (-4,-1), (-3,0), (-2,1),(-1,2),(0,3),(1,4)...\} of pairs of integers - you might think of it as a graph drawn on an infinite piece of graph paper.

Thus we will discuss, informally, some rules that allow us to say that two expressions in the -calculus always denote the same thing.

We can't always decide equality

The -calculus is a powerful formalism, capable of expressing any computation. We know we can't decide whether two programs perform the same computation, so we should not expect to be able to decide whether two -calculus expressions are equal - we may be able to say `yes for sure', `no for sure', or `I can't tell'. -conversion and equality

The -reduction rule that we have already met can also be used as a equality inference rule - in both directions.

\alpha-conversion

The variable used in a -abstraction is, to a considerable extent, arbitrary. Thus x. +\;x\;2 and y. +\;y\;2 are, intuitively, the same function: certainly they denote} the same set of pairs, given above.

There is indeed a rule of the calculus, called \alpha-conversion, which allows the above to expressions to be treated as equivalent. It is a little tricky however, since one does not want to convert x. y x to y. y y - the rule is that we may only replace the variable bound in a -abstraction by one that does not occur free in the body. The \alpha conversion rule is thus: $$ x.E \longrightarrow y.E$$ provided $$y\not\in FV(E)$$

\delta-reduction and laws

Having introduced constants to make an impure -calculus, we have to admit \delta reduction, and its inverse, into our rules for equality, together with the laws} of the algebra(s) to which the constants belong. Thus

\eta-reduction

Finally there is \eta reduction. To motivate this, let us consider the expression (+\; 1). What does it denote? Well, if we apply it to an argument 3 we obtain ((+\; 1)\; 3) which \delta reduces to 4, and likewise with the argument 4 it will reduce to 5. Thus it seems that the expression (+\; 1) is the `add one' function, commonly called the successor function. But we also can write this as a -abstraction, x. +\; 1\; x.

For the pure -calculus, the \eta rule can be stated: ( v.E)v \longrightarrow E provided v\not\in FV(E) In an impure -calculus, it is customary to restrict the \eta-rule to the case in which E denotes a function. \section{Defining substitution formally} Substitution, forming E1[E2/v], `E1 with E2 substituted for v' is a straightforward matter of rewriting sub-terms except when we come to a -abstraction which binds the variable v or a variable free in E2. We can define it by cases:\\ S1 - v[E/v] = E \\ S2 - u[E/v] = u, when u\neq v\\ S3 - c[E/v] = c, for any c\in C\\ S4 - (E1E2)[E/v] =(E1[E/v]E2[E/v])\\ S5 - v. E [E1/v] = v. E\\ S6 - ( u. E1) [E/v] = u. (E1 [E/v]), when u\neq v and u\not\in FV(E)\\ S7 - ( u. E1) [E/v] = w. ((E1[w/u]) [E/v]), when u\neq v and u\in FV(E) and w\not\in FV(E)\\ \subsection{Comments on S1-S7} Cases S1-S4 need no comment. In case S5 the variable we are substituting for is rebound in a -abstraction. Thus, inside the expression it no longer `means' the same thing - in some sense it is actually a different variable, so we should not substitute for it. In case S6, the -abstraction introduces a new variable u, but, since it does not occur in E there is no problem of confusing it with any variable occurring in E. However in case S7 there is a real problem - the new variable u introduced in the -abstraction is the same as a variable occurring free in E. The solution is to replace it throughout the -abstraction by a variable w that does not occur. We can always choose a w for S7 because we have an infinite supply of variables to choose from (and any -calculus expression only contains finitely many). \section {-calculus + syntactic sugar = a programming language?} In an influential paper, `The Next 700 Programming Languages', written in 1964, Peter Landin argued that all programming languages could and should be regarded as a way of making the -calculus palatable to human users by a coating of `syntactic sugar'. If we speak of functional languages, this view has won universal acceptance; while formally valid, it is often seen as less helpful and appropriate for procedural languages, and is rejected by most of the `logic language' community who see the rival predicate calculus} as a more appropriate basis. In Landin's view, the -calculus acts as a kind of mathematical analogue of machine code - it is a form that it is simple to reason about, in the same way that machine code is a form that it is simple for a machine to execute. \subsection{The ML formalism for a abstraction.} Let us take a preview of some `syntactic sugar' in ML and Modula-2. The calculus expression x. +\; x\; 2 is rendered in ML as \begin{verbatim} fn x => x+2 \end{verbatim} Here the is replaced by fn. This is hardly sugar at all, but reflects the poverty of the ASCII alphabet. Likewise the `.' is replaced by `$=>$', which avoids confusion with other uses of `.', e.g. as a decimal point, although we shall see that the syntactic constructions in which `$=>$' occurs in ML provide much more than simple abstraction. The last piece of syntactic sugar is to write `x+2' in place of `$+\;x\;2', in accordance with usual mathematical notation. Another piece of syntactic sugar is the let construct in ML. \begin{verbatim} let val x = 3 in x+7 end \end{verbatim} can be translated into -calculus as ( x. +\; x\; 7)3. Note that E1 and E2 in ( x. E1) E2 change their order in the let construct. This often accords with programming language practice. \subsection{A conventional language - MODULA-2} The MODULA-2 fragment: \begin{verbatim} PROCEDURE f(i:INTEGER):INTEGER; BEGIN RETURN(i+1) END f BEGIN RETURN(f(27)) END \end{verbatim} corresponds to the ML \begin{verbatim} let val f = fn(i) => i+1 in f(27) \end{verbatim} and to the -calculus ( f. f\; 27) ( i. +\; i\; 1). Note that the simple translation of MODULA-2 into the -calculus is only possible because of the restricted use of MODULA-2 in this example - essentially it is used functionally, without side-effects, and so it is easily translated. An example that used assignment, especially assignment to record-fields would be much harder to translate. \section {Landin's fixed-point combinator lets us recurse} A programming language has to allow us, in some sense, to perform an action repeatedly. In imperative languages you will have met constructs like {\bf while} and {\bf for} loops. It is not immediately apparent that the -calculus has equivalent power, but it does! Let us suppose we have an operator Y which acts upon expressions of the -calculus according to the rule $$YE = E(YE)$$ \subsection{Doing the factorial function with Y} Consider the expression $$F = u. x. (if\;(=\;x\;0)\;1\;(*\;x\;(u(-\;x\;1))))$$ For some variable n $$(YF) n = (F(YF)) n$$ using -reduction we obtain: $$ = ( x. if\;(=\;x\;0)\;1\;(*\;x\;(YF\;(-\;x\;1))))\;n$$ again using -reduction we obtain: $$ = if\;(=\;n\;0)\;1\;(*\;n\;(YF\;(-\;n\;1)))$$ that is to say, YF satisfies the equation usually given for the factorial function $$fact(n) = if\; n=0\; then\; 1\; else\; n*fact(n-1)$$ \subsectionYE is a fixed point of E} We say that YE is a fixed point} of E, and we call Y a fixpoint combinator}.\footnote{There is an interesting analogy in linear algebra - let \cal E be a function which returns the eigenvectors of a matrix. Then A(\cal E A) \equiv \cal E A, where vectors are equivalent if they have the same direction. Thus eigenvectors are a fixed point of the matrix. Indeed, if we take A to be a linear function over projective space, then \cal E is a fixpoint combinator in the same sense as Y} \subsection{Working out the factorial function} For an exercise, let us evaluate YF for a few natural numbers: $$YF(0) = if\;(=\;0\;0)\;1\;(*\;0\;(YF\;(-\;0\;1)))$$ $$ = if\;true\;1\;(*\;0\;(YF\;(-\;0\;1)))$$ $$ = 1 $$ $$YF(1) = if (=\;1\;0)\;1\;(*\;1\;(YF\;(-\;1\;1)))$$ $$ = if\;false\;1\;(*\;1\;(YF\;(-\;1\;1)))$$ $$ = (*\;1\;(YF\;(-\;1\;1)) $$ $$ = (*\;1\;(YF\;0)) $$ $$ = (*\;1\;1) $$ $$ = 1 $$ $$YF(2) = if\;(=\;2\;0)\;2\;(*\;2\;(YF\;(-\;2\;1)))$$ $$ = if\;false\;1\;(*\;2\;(YF\;(-\;2\;1)))$$ $$ = (*\;2\;(YF\;(-\;2\;1)) $$ $$ = (*\;2\;(YF\;1)) $$ $$ = (*\;2\;1) $$ $$ = 2 $$ \subsection{But we have freedom in where to reduce} Note that where chose to apply our reduction rules} is significant in working out YF. For example, we could have chosen to expand using YF = F(YF), and gone on forever. In the next section we will consider reduction strategies. \subsection{We can define Y in -calculus!} Up to now we have supposed that Y is an operator that we have made available as an addition to the -calculus. But it is not! We can define it as an expression in the -calculus: $$ Y = ( h. ( x.h (x x)) ( x.h (x x)))$$ It is left as an exercise to the reader to verify that, for any expression E of the -calculus, YE = E(YE), where Y is defined as above. \section{Doing reduction systematically} The , \eta and \delta reductions which we have considered can, in general, be applied in various places in an expression. Consider, for example: ( x.(+\;1 x))(( x.x)2). This can be reduced using -$ reduction to ( x.(+\;1 x))2 or to $+\;1 (( x.x)2). Another stage of -$ reduction arrives at $+\;1\; 2 in both cases. In this section we ask `does it matter where we chose to do the reduction'. If E is an expression of -calculus then a redex} is a sub-expression of E which can be reduced by one of our rules. \subsection{Choice of Redex is vital for Y} A close look at our definition of the Y-combinator indicates that its very mode of letting the recursion genie out of the bottle makes the choice of redex important. A simplified version of Y illustrates our difficulty: $$( x. x x)( x.x x) \reducesto_ ( x. x x)( x.x x) $$ Thus, this expression can be rewritten forever to itself using -$ reduction. Worse, consider: \label{blowup}$$( x. x x x)( x.x x x) \reducesto_ ( x. x x x)( x.x x x)( x.x x x) $$ here we have a expression that gets bigger and bigger every time it is rewritten. It should not surprise us that we might have difficulty in telling whether a sequence of reductions in the -calculus will terminate. If the -calculus is powerful enough to be a general purpose programming language, then it is powerful enough If \reducesto is a relation, then we say that \reducesto^*$ is the transitive and reflexive closure of \reducesto. The $*$ is often called the Kleene Star operation, after the mathematician Kleene. Formally, x\reducesto^*x, and if x\reducesto y and y\reducesto^*z then x\reducesto^* z. Our reduction rules can be turned into conversion rules} by taking the symmetric closure. $$E1 \longleftrightarrow E2 \; iff \; E1\longrightarrow E2 \;or\; E2\longrightarrow E1$$ Now we have shown that \reducesto^*$ is an infinite relation}, since we have exhibited a series of `reductions' a each step of which we obtain a bigger expression \ref{blowup}. In navigating this infinite maze of reduction, is there any guiding light, and can we recover if we take a wrong turn? The answers are ``Yes! and Yes!'', as is shown by the following: \subsection{Normal Forms} An expression E of the -calculus is said to be in normal form} if there is no expression E' for which E\rightarrow E'. \subsection{Church Rosser Theorem I} If E1\longleftrightarrow^* E2 then there exists an expression E such that E1\longrightarrow^* E and E2\longrightarrow^* E {\bf Corollary:} A normal form for an expression is unique. The strategy of always choosing to reduce the leftmost outermost redex first always produces a normal form if one exists. This is called normal order reduction. \subsection{Church Rosser Theorem II} If E1\longrightarrow^* E2 and E2 is a normal form, then normal order reduction will always result in reduction to E2. This result is of great importance to the design of programming languages because it implies that the vast majority of programming languages (including Scheme and ML) are wrong}!}, because they do not use a normal order reduction. A small set of languages, of which Haskell is the most important, do use normal order reduction, or reductions that are provably equivalent to it. \subsection{Weak normal forms, call-by-name and call-by-value.} Normal order reduction will perform reductions both inside and outside abstractions. If we wish to regard reduction as a model of computation then only reductions outside of abstractions can be regarded as corresponding to running a program}. Reductions inside a abstraction are in effect manipulating program, something that is usually done by a compiler. Thus we introduce the ideas of weak normal order reduction} and weak normal form} in which the only reductions performed are performed outside of any -abstraction. This is also called call by name}, and is safe} in the sense that if a weak normal form exists, weak normal order reduction will find it. Recall that normal order reduction takes the leftmost outermost redex. An alternative strategy is to take the leftmost innermost redex}. This is call by value} and is unsafe} in the sense that an infinite sequence of reductions may arise despite the fact that a (weak) normal form exists. Nevertheless most languages use some kind of call by value for almost all reductions because it is easier to implement efficiently than call by name (call by reference does not really make sense from the functional point of view). \subsection{Strictness and Laziness} A function is said to be strict} if it is certain to need the value of its argument. The sin function for example is strict. The conditional function if is non-strict, since evaluating the expression if\;E1\;E2\;E3 can be performed without evaluating both E2 and E3. Indeed it would be impossible to write recursive functions without some laziness around. \section{Semantics} Our -calculus is a fine edifice, but is it sound? A mathematician looking at it for the first time would be reminded of a sad little story of the beginning of the 20th century: the mathematician Frege had built an elegant theory of sets; Unfortunately it was inconsistent - that is to say it had no meaning, and was torpedoed and sunk by Bertrand Russell. The problem with this set theory was that it allowed expressions of the form x\in x, and in particular allowed the formalism \{x|x\not\in x\} - `the set of all sets which are not members of themselves'. Consider \{x|x\not\in x\}\in\{x|x\not\in x\}. To give a meaning to this, we must assign the value TRUE or the value FALSE to it. But in either case we have a contradiction. There is a horrid formal resemblance to x.xx, and to x.xx x.xx which is allowed in the -calculus, and a nasty suspicion must arise in the mathematical mind that it might not be possible to give a `sensible' meaning to the -calculus in which the common-sense view of -$ expressions as functions} can be substantiated. \subsection{Mending logic: Types and ZF-Set Theory} Fortunately for mathematics, set-theory was mended, and in two ways: \begin{itemize} \item Russell and Whitehead developed a theory of types} which ruled out forms such as x\in x by treating it as not well typed}. From this theory has developed, with more or less precision, the whole idea of type} in programming languages. \item Zermelo and Frankel developed a set theory in which restricted the problematic \{x|P(x)\} by requiring that the property P only be used to choose members of a set constructed by other means constructed here is a relative term}. Thus the only allowed form is \{x|x\in S and P(x)\}, where S is constructed by operations such as cartesian product or power set. \end{itemize} Russell and Whitehead's approach would require us to have a typed} -calculus. Such a edifice can be built, and is indeed a very desirable mansion that actual programming languages may inhabit. However that elegant creature the Y combinator cannot dwell therein. But it is not mathematically necessary to have a typed -calculus: it was shown in 1969 by Scott and Strachey that it is possible to devise an interpretation of the untyped -calculus in ZF set theory. %\subsection{ TO BE CONTINUED ???} \end{document} Answer: $$YE = ( h. ( x.h (x x)) ( x.h (x x))) E$$ $$ = ( x.E (x x)) ( x.E (x x)) $$ = (E ( x.E (x x) x.E (x x))) $$ = E (YE) $$

  • a countably infinite alphabet of variables, u... z, u1 ... z1, ... un ... zn, ... (It is of course only for human convenience that we allow distinct letters of the alphabet, formally and computationally we could use one sequence v1...)

    The -calculus with Constants, Primitives

    To make the -calculus suitable as a basis for practical computation we need to introduce entities which correspond to the capabilities of a real computer on which a program will run.

    A minimal capability would be provided by

    • The natural numbers: 0,1,2,3,4...
    • Two boolean constants true and false
    • Arithmetic operations +, -, *
    • A conditional function if for which the meaning of (if E E1E2) is the same as the

    with the property that (if true E1E2) evaluates to E1, and (if false E1E2) evaluates to E2, and