introduction to the lambda calculus - iowa state...
TRANSCRIPT
Com S 541
Introduction to the Lambda Calculus
Overview:! What is Computability? – Church’s Thesis! Primitive Recursive Functions! The Lambda Calculus! The Church-Rosser Property! Modeling basic programming constructs
References:! H.P. Barendregt, “The Lambda Calculus – Its Syntax and Semantics”, North-
Holland, 1984! David A. Schmidt, “The Structure of Typed Programming Languages”, MIT
Press, 1994! Carl A. Gunter, “Semantics of Programming Languages”, MIT Press, 1992
Com S 541
! Computation is usually modeled as a mapping from inputs to outputs, carried out by a formal “machine”, or program, which processes its input in a sequence of steps.
! An “effectively computable” function is one that can be computed in a finite amount of time using finite resources.
What Is Computable?
Problem
input
yes
no
output
“effectively computable”
function
program/machine
Com S 541
Church’s Thesis
! Effectively computable functions [from positive integers to positive integers] are just those definable in the lambda calculus.
Or, equivalently:
! It is not possible to build a machine that is more powerful than a Turing machine.
Church’s thesis cannot be proven because “effectively computable” is an intuitive notion, not a mathematical one. It can only be refuted by given a counter-example – a machine that can solve a problem not computable be a Turing machine.
So far, all models of effectively computable functions have shown to be equivalent to Turing machines (or the lambda calculus).
Com S 541
Uncomputability
! A problem that cannot be solved by any Turing machine in finite time (or any equivalent formalism) is called uncomputable.
Assuming Church’s thesis is true, an uncomputable problem cannot be solved by any real computer.
The Halting ProblemGiven an arbitrary Turing machine and its input tape, will the machine eventually halt?
The Halting Problem is provably uncomputable – which means that it cannot be solved in practice.
Com S 541
! Primitive recursive functions are a total recursive functions (i.e. Turing computable) and they form the smallest class of functions that:
! Includes the base functions! - the constant function, where c is introduced as
function (in most cases c=0)! - the successor function that implements counting! - the projection function, where a variable is
represented as a function with arity n! Is closed under the production rules
! Composition: Let h be a function of arity k and g1,…,gk functions with arity n, then the function denotes a function with arity n.
! Primitive recursion: Let h be a function with arity n+1 and g be a function with arity n-1, then the following equation set denotes a function fwith arity n:
Primitive Recursive Functions
nmmxnxmxxnmP ≤≤= 1,),...,,...,
1( with
const,),...,1
( == ccnxxncF with
1)( +=xxf
)],...,1
(),...,,...,1
(1
[),...,1
( nxxkgnxxghnxxf =
)),1
,...,1
(,,1
,...,1
()1,1
,...,1
(
)1
,...,1
()0,1
,...1
(
ynxxfy
nxxhy
nxxf
nxxg
nxxf
−−=+−
−=−
Com S 541
add(x, y) = x + y
! Recursive equations:add(x, 0) = xadd(x, y+1) = add(x, y) + 1
! Primitive recursive functions:
Example I – add(x, y)
x (x)P 0) add(x, 11 ==
z) y,(x,P succ z)] y,(x,P ),[succ(x'h' )z y,h(x, with ) y)add(x, y,h(x, 1) yadd(x,
33
33 o==
=+
Com S 541
Example I – The Haskell Code
-- base functions: prim_null, prim_succ, and prim_projprim_null :: Int -> Intprim_null x = 0
prim_succ :: Int -> Intprim_succ x = x + 1
-- The operator !! - list index subscript - is defined in Prelude.hsprim_proj :: ([Int], Int) -> Intprim_proj (tuple, index) = tuple!!(index - 1)
-- add( x, y ) = x + y
prim_add :: (Int, Int) -> Intprim_add (x, 0) = prim_proj([x], 1)prim_add (x, y+1) = h(x, y, prim_add(x, y))
where h(x, y, z) = (prim_succ . prim_proj)( [x, y, z], 3 )
Com S 541
Examples II – e(x, y) = xy
exp(x, y) = xy
! Recursive equations:exp(x, 0) = 1exp(x, y+1) = x * exp(x, y)
! Primitive recursive functions:1 (x)F 0) exp(x, 1
1 ==
z)) y,(x,P z), y,(x,mul(P )z y,h(x, with ) y)exp(x, y,h(x, 1) yexp(x,
33
31=
=+
Com S 541
Example II – The Haskell Code
-- prim_mul …
-- exp( x, y ) = x ** y
prim_exp :: (Int, Int) -> Intprim_exp (x, 0) = 1prim_exp (x, y+1) = h(x, y, prim_exp(x, y))
where h(x, y, z) = prim_mul( prim_proj( [x, y, z], 1 ),prim_proj( [x, y, z], 3 ) )
Com S 541
Example III – pred(x) = x - 1
pred(x) = x - 1
! Recursive equations:pred(0) = 0pred(x+1) = x
! Primitive recursive functions:
! Note: for all x >= 1 it holds pred(succ(x))=succ(pred(x))=x
0 pred(0) =
pred(x)) (x,P 1)pred(x 21=+
Com S 541
The Lambda Calculus
! Lambda calculus is a language with clear operational and denotationalsemantics capable of expressing algorithms. Also it forms a compact language to denote mathematical proofs.
! Logic provides a formal language in which mathematical statements can be formulated and provides deductive power to derive these. Type theory is a formal system, based on lambda calculus and logic, in which statements, computable functions and proofs all can be naturally represented.
! The lambda calculus is a good medium to represent mathematics on a computer with the aim to exchange and store reliable mathematical knowledge.
Com S 541
The (Untyped) Lambda Calculus
! The Lambda Calculus was invented by Alonzo Church [1932] as a mathematical formalism for expressing computation by functions.
Syntax: e::= x a variable| λx.e an abstraction (function )| e1 e2 a (function) application
(Operational) Semantics:α−conversion (renaming): λx.e↔ λy.[y/x]e where y is not free in eβ-reduction (application): (λx.e1)e2"[e2/x]e1 avoiding name captureη-reduction: λx.(ex)" e if x is not free in e
! The lambda calculus can be viewed as the simplest possible pure functional programming language.
Com S 541
Beta Reduction
! Beta reduction is the computational engine of the lambda calculus:
Define: I≡ λx.x
Now consider:II= (λx.x)(λx.x) " [(λx.x)/x]x β-reduction
= (λx.x) substitution= I
! We can implement most lambda expressions directly in Haskell:i = \x -> x? i 55? i i 55
Com S 541
Free and Bound Variables
! The variable x is bound by the enclosing λ in the expression λx.e. A variable that is not bound, is free:
fv(x) = x
fv(λx.e) = fv(e)\xfv(e1 e2 ) = fv(e1)∪ fv(e2)
! An expression with no free variables is closed (otherwise it is open). For example, y is bound and x is free in the (open) expression λy.xy.
Syntactic substitution will not always work:(λx.λy.xy)y " [y/x](λy.xy) β-reduction
≠ (λy.yy) incorrect substitution!Since y is already bound in (λy.xy), we cannot directly substitute y for x.
Com S 541
Substitution
! We must define substitution carefully to avoid name capture:
[e/x]x= e
[e/x]y= y if x≠ y[e/x](e1 e2)= ([e/x]e1)([e/x]e2)
[e/x](λx.e1)= (λx.e1)[e/x](λy.e1)= (λy.[e/x]e1) if x≠ yand y ∉ fv(e)
[e/x](λy.e1)= (λz.[e/x][z/y]e1) if x≠ yand z ∉ (fv(e)∪ fv(e1))
Consider:(λx.((λy.x)(λx.x))x)y " [y/x]((λy.x)(λx.x))x
= ((λz.y)(λx.x))y
Com S 541
Alpha Conversion
! Alpha conversions allows one to rename bound variables.
! A bound name x in the lambda abstraction (λx.e)may be substituted by any other name y, as long as there are no free occurrences of y in e:
Consider:(λx.λy.xy)y" (λx.λz.xz)y α-conversion
" [y/x](λz.xz) β-reduction" (λz.yz)= y η-reduction
Com S 541
Eta Reduction
! η-reductions allows one to remove “redundant lambdas”.
! Suppose that fis a closed expression (i.e., xdoes not occur free in f). Then:
(λx.fx)y " ([y/x]f)([y/x]x)= fy β-reduction
More generally, this will hold whenever xdoes not occur free in f. In such cases, we can always rewrite (λx.fx)as f.
Com S 541
Normal Forms
! A lambda expression is in normal form if it can no longer be reduced by the β- or η-reduction rules.
! But not all lambda expressions have normal forms!
Ω = (λx.xx)(λx.xx) "[(λx.xx)/x](xx)= (λx.xx)(λx.xx) β-reduction"(λx.xx)(λx.xx) β-reduction"(λx.xx)(λx.xx) β-reduction"(λx.xx)(λx.xx) β-reduction"...
! Reduction of a lambda expression to a normal form is analogous to a Turing machine halting or a program terminating.
Com S 541
Evaluation Order
! Most programming languages are strict, that is, all expressions passed to a function call are evaluated before control is passed to the function.
! Most modern functional languages, on the other hand, use lazy evaluation, that is, expressions are only evaluated when they are needed.
Consider: square n = n * n
Applicative-order reduction:square (2 + 5) # square 7 # 7 * 7 # 49
Normal-order reduction:square (2 + 5) # (2 + 5) * (2 + 5) # 7 * (2 + 5) # 7 * 7 # 49
Com S 541
Applicative Order Reduction
! Motivation:! Modeling call-by-value in programming languages! In function calls, evaluate arguments then invoke function
! In the lambda-calculus, this means:! In (e1 e2), reduce e2 to normal form using applicative order reduction! Then reduce e1 to normal form using applicative order reduction! If e1 is a lambda abstraction, do beta reduction, and reduce the result to normal
form using applicative order reduction
! Syntax makes it easy:! Write expression using fully parenthesized notation! Always perform rightmost beta reduction by! Repeatedly scanning for rightmost (left parenthesis) occurrence of ((λx . e1) e2)! Note, this includes reduction of primitives, e.g. ((add 1) 2)
Com S 541
Applicative Order Example
! Consider:((λx . ((λy . add y y) (mul x x))) (sub 3 1))
! Applicative order reduction gives((λx . ((λy . add y y) (mul x x))) (sub 3 1))((λx . (add (mul x x) (mul x x))) (sub 3 1))((λx . (add (mul x x) (mul x x))) 2)(add (mul 2 2) (mul 2 2))(add 4 (mul 2 2))(add 4 4)8
Com S 541
The Church-Rosser Property
“If an expression can be evaluated at all, it can be evaluated by consistently using normal- order evaluation. If an expression can be evaluated in several different orders (mixing normal-order and applicative-order reduction), then all of these evaluation orders yield the same result”.
! So, evaluation order “does not matter” in the lambda calculus. However, applicative order reduction may not terminate, even if a normal form exists!
(λx.y)((λx.xx)(λx.xx))
Applicative-order reduction Normal-order reduction"(λx.y)((λx.xx)(λx.xx)) " y"(λx.y)((λx.xx)(λx.xx))"...
Com S 541
Currying
! Since a lambda abstraction only binds a single variable, functions with multiple parameters must be modeled as curried higher-order functions. This method is named after the logician H. B. Curry, who popularized the approach.
! To improve readability, multiple lambdas can be suppressed, so:
λxy.x= λx.λy.xλbxy.bxy= λb.λx.λy.(bx)y
Com S 541
Representing Booleans
! Although the lambda calculus is extremely sparse, most (sequential) programming constructs can be built up as lambda expressions.
Define: TRUE ≡ λxy.xFALSE ≡ λxy.ynot ≡ λb.bFALSE TRUEifbthenxelsey ≡ λbxy.bxy
Then: notTRUE = (λb.bFALSE TRUE)(λxy.x)" (λxy.x)FALSE TRUE" FALSE
ifTRUE thenxelsey = (λbxy.bxy)(λxy.x)xy" (λxy.x)xy" x
Com S 541
Representing Tuples
! Although tuples are not supported by the lambda calculus, they can easily be modeled as higher-order functions that “wrap” pairs of values. n-tuples can be modeled by composing pairs ...
Define: pair ≡ (λxyz.zxy)first ≡ (λp.pTRUE)second ≡ (λp.pFALSE)
Then:(1,2)= pair12" (λz.z12)
In Haskell:t= \x-> \y-> x ?first(pair12)
f= \x-> \y-> y 1
pair= \x-> \y-> \z-> zxy ?first(second(pair1(pair23)))
first= \p-> pt 2
second= \p-> pf
Com S 541
Church Numbers
! A number n is represented by a functional, which applies an argument function n-times to another argument. The number zero (0) is represented by a functional that yields the identity function for its argument.
Define: n ≡ λs.λz.s(n)z0 ≡ λs.λz.zsucc ≡ λn.λs.λz.s(nsz)iszero ≡ λn.n(λx.FALSE)TRUEadd ≡ λm .λn.m succn
Then: 1= succ0= (λn.λs.λz.s(nsz))(λs.λz.z)" λs.λz.s((λf.λx.x)sz)" λs.λz.s((λx.x)z)" λs.λz.sz
Com S 541
De Brujin Indices
! Names in the λ-calculus provide a way to relate binding and applied occurrences of variables, but introduce problems in substitutions, and furthermore hide the internal similarity between α-equivalent expressions.
! In 1972 de Brujin proposed a new version of the lambda calculus, in which these problems do not occur. Instead denoting variables by names, de Brujin used indices, which are non-negative integer values indicating the number of abstraction levels between an applied occurrence of a variable and its corresponding binding occurrence.
Com S 541
A Tree of a Lambda-expression
! Consider the expressionλx . λy . (λx . x y) x
λx
λy
@
xλx
@
x y
λ
λ
@
1λ
@
0 1
De Brujin encoding:
Each arrow is uniquely described by the number of “λ” nodes between its source and its target.