the lambda calculus
description
Transcript of the lambda calculus
the lambda calculus
David WalkerCS 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
x is boundin the term \x.x y
y is free 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
target = lambda calculus
translate/compile
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) = xtranslate (\x.e) = \x.translate etranslate (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. ftest = \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. fand = \b.\c. b c fls
and tru tru -->* tru tru fls -->* tru
booleans
tru = \t.\f. t fls = \t.\f. fand = \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 regardlessof 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 secfst = \p. p trusec = \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 likeso this is true.
operational semantics again
• Is this the only possible operational semantics?
e1 --> e1’e1 e2 --> e1’ e2
e2 --> e2’v e2 --> v e2’
(\x.t) v --> t [v/x]
alternatives
e1 --> e1’e1 e2 --> e1’ e2
e2 --> e2’v e2 --> v e2’
(\x.t) v --> t [v/x]
e1 --> e1’e1 e2 --> e1’ e2
(\x.t) e --> t [e/x]
call-by-value call-by-name
alternatives
e1 --> e1’e1 e2 --> e1’ e2
e2 --> e2’v e2 --> v e2’
(\x.t) v --> t [v/x]
e1 --> e1’e1 e2 --> e1’ e2
(\x.t) e --> t [e/x]
call-by-value full beta-reduction
e2 --> e2’e1 e2 --> e1 e2’
e --> e’\x.e --> \x.e’
alternatives
e1 --> e1’e1 e2 --> e1’ e2
e2 --> e2’v e2 --> v e2’
(\x.t) v --> t [v/x]
e1 --> e1’ e1 ≠ ve1 e2 --> e1’ e2
(\x.t) e --> t [e/x]
call-by-value normal-order reduction
e2 --> e2’v e2 --> v e2’
e --> e’\x.e --> \x.e’
alternatives
e1 --> e1’e1 e2 --> e1’ e2
e2 --> e2’v e2 --> v e2’
(\x.t) v --> t [v/x]
call-by-value right-to-left call-by-value
e1 --> e1’e1 v --> e1’ v
e2 --> e2’e1 e2 --> e1 e2’
(\x.t) v --> t [v/x]
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