Write a Haskell datatype Term
that represents a
λ-calculus term.
Write a function isFree :: String -> Term -> Bool
that determines whether a variable occurs free in a term.
Write a function freeVars :: Term -> [String]
that computes the set of free variables in a term.
Evaluate the following lambda-calculus substitutions:
((xyz)[x := y])[y := z]
((λx.xyz)[x := y])[y := z]
(λy.yyz)[x := yz]
(λy.x(λx.x))[x := λy.xy]
(y(λv.xv))[x := λy.vy]
Write a Haskell function subst :: Term -> String ->
Term -> Term
that performs substitution in a
lambda-calculus term.
True or false? If false, provide a counterexample.
M[x := P][y := Q] ≡ M[y := Q][x := P]
FV(M[x := N]) = FV(M) ∪ FV(N)
M[x := v][v := P] ≡ M[x := P]
Which of the following terms are α-equivalent to (λz.zxz)((λy.xy)x) ?
(λy.yxy)((λz.xz)x)
(λx.xyx)((λz.yz)y)
(λy.yxy)((λy.xy)x)
(λv.(vx)v)((λu.uv)x)
Write a function alphaEq :: Term -> Term -> Bool
that determines whether two terms are α-equivalent.
Reduce the following terms to β-normal form:
(λz.zxz)((λy.xy)x)
(λx.x(x(yz))x)(λu.uv)
(λxy.xyy)(λu.uyx)
Recall that normal-order reduction always reduces the leftmost outermost β-redex in a term. An outermost redex is one that is not contained in any other redex.
Write a Haskell function leftmost :: Term -> Maybe Term
that finds and returns the leftmost outermost β-redex in a term, or
Nothing
if there is none.
Call-by-name reduction is like normal-order reduction, but does not perform reductions inside an abstraction. For example, this evaluation strategy will perform no more reductions on the term λx.(λy.y)z. The normal-order strategy will reduce this term to λx.z.
Write a Haskell function evalByName :: Term -> Term
that evaluates a term using call by name. (If the term has no normal
form, your function may not terminate.)
Call-by-value reduction is like call-by-name reduction, but is strict: it evaluates a function's argument before calling the function.
Write a Haskell function evalByValue :: Term -> Term
that evaluates a term using call by value.
In the lambda calculus, the Church booleans are defined as follows:
true = λx.λy.x
false = λx.λy.y
We can now define a function
if = λb.λx.λy.bxy
that works like if/then/else in Haskell. For example,
if false (aa) (bb) → bb
Define functions for logical and, or, and not.
In the lambda calculus, we may represent the pair (x, y) by the function
λb.if b x y
Define a function pair that takes two values x and y and returns a pair.
Define functions fst and snd that return the first and second elements of a pair.
The Church numerals are one possible way to encode the integers in the lambda calculus:
0 = λf.λx.x
1 = λf.λx.fx
2 = λf.λx.f(fx)
…
Write a Haskell function makeNum
:: Int -> Term
that constructs a Church numeral.
Write a Haskell function decode
:: Term -> Maybe Int
that converts a Church numeral to a
Haskell integer. If the term is not a Church numeral, return
Nothing
.
In the lambda calculus, define the following functions on the Church numerals:
succ – given n, computes (n + 1)
plus – given m and n, computes (m + 1)
times – given m and n, computes (m · n)
exp – given m and n, computes mn
iszero – given m, returns true if m = 0