480 likes | 664 Views
the lambda calculus. David Walker CS 441. the lambda calculus. Originally, the lambda calculus was developed as a logic by Alonzo Church in 1932 Church says: “There may, indeed, be other applications of the system than its use as a logic.” Dave says: “I’ll say”. Reading.
E N D
the lambda calculus David Walker CS 441
the lambda calculus • Originally, the lambda calculus was developed as a logic by Alonzo Church in 1932 • Church says: “There may, indeed, be other applications of the system than its use as a logic.” • Dave says: “I’ll say”
Reading • Pierce, Chapter 5
functions • essentially every full-scale programming language has some notion of function • the (pure) lambda calculus is a language composed entirely of functions • we use the lambda calculus to study the essence of computation • it is just as fundamental as Turing Machines
syntax t,e ::= x (a variable) | \x.e (a function; in ML: fn x => e) | e e (function application)
syntax • the identity function: • \x.x • 2 notational conventions: • applications associate to the left (like in ML): • “y z x” is “(y z) x” • the body of a lambda extends as far as possible to the right: • “\x.x \z.x z x” is “\x.(x \z.(x z x))”
terminology • \x.t • \x.x y the scope of x is the term t y is free in the term \x.x y x is bound in the term \x.x y
CBV operational semantics • single-step, call-by-value OS: t --> t’ • values are v ::= \x.t • primary rule (beta reduction): • t [v/x] is the term in which all free occurrences of x in t are replaced with v • this replacement operation is called substitution • we will define it carefully later in the lecture (\x.t) v --> t [v/x]
operational semantics • search rules: • notice, evaluation is left to right e1 --> e1’ e1 e2 --> e1’ e2 e2 --> e2’ v e2 --> v e2’
Example (\x. x x) (\y. y)
Example (\x. x x) (\y. y) --> x x [\y. y / x]
Example (\x. x x) (\y. y) --> x x [\y. y / x] == (\y. y) (\y. y)
Example (\x. x x) (\y. y) --> x x [\y. y / x] == (\y. y) (\y. y) --> y [\y. y / y]
Example (\x. x x) (\y. y) --> x x [\y. y / x] == (\y. y) (\y. y) --> y [\y. y / y] == \y. y
Another example (\x. x x) (\x. x x)
Another example (\x. x x) (\x. x x) --> x x [\x. x x/x]
Another example (\x. x x) (\x. x x) --> x x [\x. x x/x] == (\x. x x) (\x. x x) • In other words, it is simple to write non terminating computations in the lambda calculus • what else can we do?
We can do everything • The lambda calculus can be used as an “assembly language” • We can show how to compile useful, high-level operations and language features into the lambda calculus • Result = adding high-level operations is convenient for programmers, but not a computational necessity • Result = make your compiler intermediate language simpler
Let Expressions • It is useful to bind intermediate results of computations to variables: let x = e1 in e2 • Question: can we implement this idea in the lambda calculus? source = lambda calculus + let translate/compile target = lambda calculus
Let Expressions • It is useful to bind intermediate results of computations to variables: let x = e1 in e2 • Question: can we implement this idea in the lambda calculus? translate (let x = e1 in e2) = (\x.e2) e1
Let Expressions • It is useful to bind intermediate results of computations to variables: let x = e1 in e2 • Question: can we implement this idea in the lambda calculus? translate (let x = e1 in e2) = (\x. translate e2) (translate e1)
Let Expressions • It is useful to bind intermediate results of computations to variables: let x = e1 in e2 • Question: can we implement this idea in the lambda calculus? translate (let x = e1 in e2) = (\x. translate e2) (translate e1) translate (x) = x translate (\x.e) = \x.translate e translate (e1 e2) = (translate e1) (translate e2)
booleans • we can encode booleans • we will represent “true” and “false” as functions named “tru” and “fls” • how do we define these functions? • think about how “true” and “false” can be used • they can be used by a testing function: • “test b then else” returns “then” if b is true and returns “else” if b is false • the only thing the implementation of test is going to be able to do with b is to apply it • the functions “tru” and “fls” must distinguish themselves when they are applied
booleans • the encoding: tru = \t.\f. t fls = \t.\f. f test = \x.\then.\else. x then else
booleans tru = \t.\f. t fls = \t.\f. f test = \x.\then.\else. x then else eg: test tru (\x.t1) (\x.t2) -->* (\t.\f. t) (\x.t1) (\x.t2) -->* \x.t1
booleans tru = \t.\f. t fls = \t.\f. f and = \b.\c. b c fls and tru tru -->* tru tru fls -->* tru
booleans tru = \t.\f. t fls = \t.\f. f and = \b.\c. b c fls and fls tru -->* fls tru fls -->* fls
booleans • what is wrong with the following translation? translate true = tru translate false = fls translate (if e1 then e2 else e3) = test (translate e1) (translate e2) (translate e3) ...
booleans • what is wrong with the following translation? translate true = tru translate false = fls translate (if e1 then e2 else e3) = test (translate e1) (translate e2) (translate e3) ... -- e2 and e3 will both be evaluated regardless of whether e1 is true or false -- the target program might not terminate in some cases when the source program would
pairs • would like to encode the operations • create e1 e2 • fst p • sec p • pairs will be functions • when the function is used in the fst or sec operation it should reveal its first or second component respectively
pairs create = \fst.\sec.\bool. bool fst sec fst = \p. p tru sec = \p. p fls fst (create tru fls) -->* fst (\bool. bool tru fls) -->* (\bool. bool tru fls) tru -->* tru
and we can go on... • numbers • arithmetic expressions (+, -, *,...) • lists, trees and datatypes • exceptions, loops, ... • ... • the general trick: • values will be functions – construct these functions so that they return the appropriate information when called by an operation
Formal details • In order to be precise about the operational semantics of the lambda calculus, we need to define substitution properly • remember the primary evaluation rule: (\x.t) v --> t [v/x]
substitution: a first try • the definition is given inductively: x [t/x] = t y [t/x] = y (if y ≠ x) (\y.t’) [t/x] = \y.t’ [t/x] t1 t2 [t/x] = (t1 [t/x]) (t2 [t/x])
substitution: a first try • This works well 50% of the time • Fails miserably the rest of the time: (\x. x) [y/x] = \x. y • the x in the body of (\x. x) refers to the argument of the function • a substitution should not replace the x • we got “unlucky” with our choice of variable names
substitution: a first try • This works well 50% of the time • Fails miserably the rest of the time: (\x. z) [x/z] = \x. x • the z in the body of (\x. z) does not refer to the argument of the function • after substitution, it does refer to the argument • we got “unlucky” with our choice of variable names again!
calculating free variables • To define substitution properly, we must be able to calculate the free variables precisely: FV(x) = {x} FV(\x.t) = FV(t) / {x} FV(t1 t2) = FV(t1) U FV(t2)
substitution x [t/x] = t y [t/x] = y (if y ≠ x) (\y.t’) [t/x] = \y.t’ (if y = x) (\y.t’) [t/x] = \y.t’ [t/x] (if y ≠ x and y FV(t)) t1 t2 [t/x] = (t1 [t/x]) (t2 [t/x])
substitution • almost! But the definition is not exhaustive • what if y ≠ x and y FV(t) in the case for functions: (\y.t’) [t/x] = \y.t’ (if y = x) (\y.t’) [t/x] = \y.t’ [t/x] (if y ≠ x and y FV(t))
alpha conversion • the names of bound variables are unimportant (as far as the meaning of the computation goes) • in ML, there is no difference between • fn x => x and fn y => y • we will treat \x. x and \y. y as if they are (absolutely and totally) indistinguishable so we can always use one in place of the other
alpha conversion • in general, we will adopt the convention that terms that differ only in the names of bound variables are interchangeable • ie: \x.t == \y. t[y/x] (where this is the latest version of substitution) • changing the name of a bound variable is called alpha conversion
substitution, finally x [t/x] = t y [t/x] = y (if y ≠ x) (\y.t’) [t/x] = \y.t’ [t/x] (if y ≠ x and y FV(t)) t1 t2 [t/x] = (t1 [t/x]) (t2 [t/x]) we use alpha-equivalent terms so this constraint can always be satisfied. We pick y as we like so this is true.
operational semantics again • Is this the only possible operational semantics? (\x.t) v --> t [v/x] e1 --> e1’ e1 e2 --> e1’ e2 e2 --> e2’ v e2 --> v e2’
alternatives (\x.t) e --> t [e/x] (\x.t) v --> t [v/x] e1 --> e1’ e1 e2 --> e1’ e2 e1 --> e1’ e1 e2 --> e1’ e2 e2 --> e2’ v e2 --> v e2’ call-by-value call-by-name
alternatives (\x.t) e --> t [e/x] (\x.t) v --> t [v/x] e1 --> e1’ e1 e2 --> e1’ e2 e1 --> e1’ e1 e2 --> e1’ e2 e2 --> e2’ e1 e2 --> e1 e2’ e2 --> e2’ v e2 --> v e2’ e --> e’ \x.e --> \x.e’ call-by-value full beta-reduction
alternatives (\x.t) e --> t [e/x] (\x.t) v --> t [v/x] e1 --> e1’ e1 ≠ v e1 e2 --> e1’ e2 e1 --> e1’ e1 e2 --> e1’ e2 e2 --> e2’ v e2 --> v e2’ e2 --> e2’ v e2 --> v e2’ e --> e’ \x.e --> \x.e’ call-by-value normal-order reduction
alternatives (\x.t) v --> t [v/x] (\x.t) v --> t [v/x] e1 --> e1’ e1 e2 --> e1’ e2 e1 --> e1’ e1 v --> e1’ v e2 --> e2’ v e2 --> v e2’ e2 --> e2’ e1 e2 --> e1 e2’ call-by-value right-to-left call-by-value
summary • the lambda calculus is a language of functions • Turing complete • easy to encode many high-level language features • the operational semantics • primary rule: beta-reduction • depends upon careful definition of substitution • many evaluation strategies