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:
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
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.
A countably infinite set of variables. We shall use as variables any sequence of letters, digits or the "_" character which begins with a letter, or any sequence of the "sign" characters,
A set of constants. In our case these will be
If E1 and E2 are expressions of
the -calculus, then the application of
E1 to E2 is written
(E1E2), and is an expression.
-abstraction:
If v is a variable, and E is an expression
of the
-calculus, then the abstraction
v. E is an expression of the
-calculus.
The expression E is referred to as the body of the
abstraction
v.E
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:
A variable v is a symbol which (eventually) has an
associated value. Variables in the -calculus
play much the same role as identifiers in a programming language. We'll see
exactly how quite shortly, when we talk about
-reduction.
Among the constants, the natural numbers are self-explanatory - the
a given sequence of characters such as 123 denote a natural
number, using the base-10 convention. Note however that a
maximally-faithful computational representation of the -calculus will require us to use arbitrary precision
arithmetic so that the only limit on the size of a natural number will
be the storage capacity of the machine.
The boolean constants should also be self-explanatory.
The System constant serves to provide an interface to the built-in capabilities of the computer and its software. For example, the expression (System '+') performs addition. Don't be alarmed about the cumbersome nature of this - normally we make sure that the value of the variable + is set to be (System '+').
The purpose of quoted expressions is to support the ability to mechanise
reasoning about the -calculus within the
calculus itself.
An application (E1E2) can be thought
of as meaning "apply the function E1 to the argument
E2", as, for example, we may apply the function
sin to the argument 0 when we write the expression
sin 0 in mathematics. Likewise, in the -calculus, if the variable
abs has as its value the absolute-value-function, then
(abs x) denotes the absolute value of the value denoted by the
variable x.
But, you may say, most people want to have functions that take more than one argument - how is this accomplished? Let's consider addition, and in a context in which the variable + denotes addition. The sum of two natural numbers 10 and 3 can be expressed as ((+ 10) 3) - we understand this as meaning that the expression (+ 10) evaluates to a function we can call "add 10". This function is then applied to 3, obtaining 13.
An abstraction v.E can be thought of
as meaning "that function which maps the variable v to the
expression E. Remember that E is an expression, in which
v may, and usually will, occur.
For example,
v.v means "that function which
maps v to itself", that is to say, the identity function.
Likewise, in a context in which + denotes addition, the expression
x.((+ 10) x) means "that function which maps
x to 10+x", that is the function "add 10". Note that we
have already encountered this "add 10" function in a different form above.
We shall address the issue of what it means for these two expressions to
denote the same function when we consider the eta-equivalence
rule below.
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
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
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
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
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.
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:
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
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.
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) = \{\}.
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?'.
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.
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.
The variable used in a
There is indeed a rule of the -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.
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
x. (+ (+ x 3) 9) \equiv
x.
(+ x 12)$$
\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 To make the
A minimal capability would be provided by
with the property that
(if true E1E2)
evaluates to E1, and
(if false E1E2)
evaluates to E2, and
-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) $$
The
-calculus with Constants, Primitives
-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.