LAMBDA CALCULUS Introduction The lambda calculus is a notation for expressing computations. Because of its simplicity, it allows us to focus on conceptual issues rather than syntactic frills. To fully appreciate lambda calculus requires two insights: how lambda calculus is rich enough to express the entire set of computable functions and how the meaning of a lambda expression can be unambiguously determined by a small set of simple rules. The first insight we will call the "richness" problem; the second is called the "semantics" problem. Before discussing them, we first need to know what a lambda expression looks like. Lexical Tokens 'L' - the abstraction operator (pronounced "lambda") '(', ')', '.' - three punctuation characters id - a set of identifiers constructed from alphabetic characters and numbers In formal publications, the 'L' symbol is written using a lower-case Greek lambda character. Syntax _/\_ (a "lambda term" or "lambda expression") <- id (a "variable") | (L id . _/\_) (an "abstraction") | (_/\_ _/\_) (an "application") ; This syntax is unambigous. It does, however, require numerous parentheses. Later, we will introduce some guidelines for relaxing the syntactic rules. Definition A "variable occurrence" is an instance of an identifier token anywhere within a lambda expression. Definition The "abstraction variable" for an abstraction is the identifier occurring in the abstraction between the 'L' and the '.'. Definition The "body" of an abstraction is the lambda expression that occurs to the right of its period token. Definition The "binding abstraction" for a variable occurrence is the most closely surrounding abstraction containing the occurrence whose abstraction identifier is the same as that of the variable occurrence. There may be no binding abstraction for a variable occurrence. If, however, such an abstraction occurs, it is unique. Note that the variable 'x' is bound in the following abstraction but that 'y' is not. (Lx.y) Definition A variable occurrence is "bound" in a lambda expression if a binding abstraction exists for it in the expression. Definition A variable is "bound" in a lambda expression if any of its occurrences are bound. Definition A variable occurrence is "free" in a lambda expression if no binding abstraction exists for it. Definition A variable is free in a lambda expression if any of its occurrences are free. Assertion A variable can be both free and bound in the same lambda expression. ((Lx.x) x) Discussion Recursive functions are sufficiently rich to model all interesting computations. We would like to show how lambda expressions can be used as a formal notation for recursive functions. In particular, abstractions will serve as function definitions, and applications will serve as function evaluations. For example, the expression ((Lx. x*x) 3) can be interpreted as denoting the evaluation of a defined function with an argument which is the natural number denoted by '3'. The defined function takes an argument and multiplies the argument by itself. We assume that the symbols '3' and '*' have been previously given an interpretation corresponing to how we believe arithmetic should work. Later we will see how we can give lambda calculus definitions for these symbols. Besides arithmetic, we will see how lambda expressions can be used to define boolean values, logical operators, conditional expressions, and recursion. These are the concepts from which recursive functions definitions are built. Together they will serve to demonstrate that, in fact, lambda expressions are rich enough to model recursive functions and therefore all computable functions. Evaluation Rules We will define the semantics so that lambda expressions obey three "intuitive" rules. 1) The particular variable names that are chosen have no effect on the meaning of a abstraction being defined. This is called the alpha equivalence rule. 2) We can evaluate an application by replacing occurrences of its bound variables with the supplied arguments. This is called the beta reduction rule. 3) Substituting an argument variable into an abstraction whose bound variable is the same variable yields a lambda expression identical with the body of the abstraction. This is called the eta reduction rule. All of these rules depend on the idea of replacing a variable by a lambda expression. In normal circumstances, this amounts to textually replacing occurrences of the variable by the lambda expression. "Normal circumstances" means that there is no collision of names between the lambda expressions. This idea can be made precise using an operation called substitution. Notation If M and N are lambda expressions and x is a variable, then the operation of "substituting" N for x in M is expressed using the following notation. M[x:=N] Definition Substitution is a syntactic operation on lambda expressions. For the substitution M[x:=N], we supply three things: a lambda expression to be substituted into (M), a variable to be substituted for (x), and a lambda expression to use to replace appropriate variable occurrences (N). The result of the substitution operation M[x:=N] is defined by cases. (In this definition, the operator '->' can be read as "yields" or "leads to" or "results in". If (M is a variable) { if (M == x) /* The result is the substituted expression */ M[x:=N] -> x[x:=N] -> N else /* (M != x) */ /* No change */ M[x:=N] -> M } else if (M is an abstraction (Lv. B) where v is a variable and B is a lambda expression) { if (v == x) /* No change */ M[x:=N] -> M else { /* (v != x) */ if (x does not occur free in B) /* No change */ M[x:=N] -> M else { /* (x occurs free in B) */ if (v does not occur free in N) /* Make the substitution into the body of the abstraction */ M[x:=N] -> (Lv. B)[x:=N] -> (Lv. B[x:=N]) else /* (v occurs free in N) */ /* Invent a new variable z; substitute z for v; then substitute N for x */ M[x:=N] -> (Lv. B)[x:=N] -> (Lz. B[v:=z][x:=N]) } } } else /* M is an application (B C) */ /* Substitute into the two parts of the application independently */ M[x:=N] -> (B C)[x:=N] -> (B[x:=N] C[x:=N]) Semantics Ultimately, the meaning of a lambda expression resolves to the operations of primitive functions on primitive data. Therefore, to give a semantics to lambda expressions, we have two responsibilities: to provide the primitive functions and data and to show how to simplify complex expressions until only the primitive operations and data are left. The primitive operations and data will be demonstrated by showing how integers, truth values, arithmetic, and logic can be modeled with lambda expressions. Simplification will be provided in terms of the three rules mentioned above: alpha equivalence, beta reduction, and eta reduction. Syntactic Sugar - Drop the outermost pair of parentheses for abstractions and applications. For example, "(f x)" becomes "f x". - Assuming that all vi are different, abbreviate (Lv1.(Lv2.(... (Lvk.M) ...))) as (Lv1, v2, ..., vk. M). - Abbreviate (... ((M N1) N2) ... Nk) as M N1 N2 ... Nk; that is, allow applications to associate to the left. Notation || M || - the number of tokens in the lambda expression M M == N - lambda expression M is syntactically equal to lambda expression N M != N - lambda expression M is syntactically distinct from lambda expression N