the lambda calculus David Walker CS 441

advertisement
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
| \x.e
|ee
(a variable)
(a function; in ML: fn x => 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
the scope of x is the term t
• \x.x y
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):
(\x.t) v --> t [v/x]
– 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
operational semantics
• search rules:
e1 --> e1’
e1 e2 --> e1’ e2
e2 --> e2’
v e2 --> v e2’
• notice, evaluation is left to right
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, highlevel 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
(if y ≠ x)
x [t/x] = t
y [t/x] = y
(\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
(if y ≠ x)
x [t/x] = t
y [t/x] = y
(\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
(\x.t) v --> t [v/x]
e1 --> e1’
e1 e2 --> e1’ e2
e2 --> e2’
v e2 --> v e2’
• Is this the only possible operational
semantics?
alternatives
(\x.t) v --> t [v/x]
(\x.t) e --> t [e/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) v --> t [v/x]
e1 --> e1’
e1 e2 --> e1’ e2
e2 --> e2’
v e2 --> v e2’
call-by-value
(\x.t) e --> t [e/x]
e1 --> e1’
e1 e2 --> e1’ e2
e2 --> e2’
e1 e2 --> e1 e2’
e --> e’
\x.e --> \x.e’
full beta-reduction
alternatives
(\x.t) v --> t [v/x]
e1 --> e1’
e1 e2 --> e1’ e2
e2 --> e2’
v e2 --> v e2’
call-by-value
(\x.t) e --> t [e/x]
e1 --> e1’
e1 ≠ v
e1 e2 --> e1’ e2
e2 --> e2’
v e2 --> v e2’
e --> e’
\x.e --> \x.e’
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’
call-by-value
e2 --> e2’
e1 e2 --> e1 e2’
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
Download