Essentials of Programming Languages
Lecture #2
Data Types
Building blocks for data types
Products
Closely Related: Records
{ f1 : A1, ..., fn : An }
r.fi
Tuples in big-step semantics
e ::= ... | (e, ..., e) | proj n e
Exp = ... | Tuple (List Exp) | Proj Int Exp
Val = ... | VTuple (List Val)
eval r (Tuple e*) =
VTuple (eval* r e*) -- evaluate each e in e*
eval r (Proj i e) =
let (VTuple v*) = eval r e in
v* [i] -- ith element of list
Call-by-name tuples / Lazy tuples
Val' = ... | VTuple r (List Exp)
eval' r (Tuple e*) =
VTuple r e*
eval' r (Proj i e) =
let (VTuple r' e*) = eval' r e in
eval' r' (e* [i])
Tuples in Small-step semantics
e ::= ... | (e,...,e) | proj i e
v ::= ... | (v,...,v)
introduction: no reduction rules, but context
elimination: reduction rules & context
proj i (v1,..., vi, ..., vn) --> vi
S ::= ... | (v1...,vi, [ ], e, ...) | proj i [ ]
Sums
Closely related: Variants
ci e
c1 * + c2 * + ...
Sums in big-step semantics
e ::= ... | ini e | case e of ... ini x => ei ...
Exp = ... | Inj Int Exp | Case Exp (List Match)
Match = Match Int Ident Exp
Val = ...| VInj Int Val
eval r (Inj i e) =
VInj i (eval r e)
eval r (Case e m*) =
let (VInj i v) = eval r e in
let (Match i x e') = m*[i] in
eval (put r x v) e'
Lazy sum
Val' = ... | VInj i Closure
eval' r (Inj i e) =
VInj i (Clos r e)
eval' r (Case e m*) =
let (VInj i c) = eval' r e in
let (Match i x ei) = m*[i] in
-- either evaluate c to v at this point
-- or change the environment to bind variables to closures
eval' (put r x v) e'
Encoding of Boolean
Boolean = * + *
false = inr ()
true = inl ()
if e1 e2 e3 =
case e1 of | inl() => e2 | inr() => e3
Recursive data structures
Nat = * + Nat
IntList = * + Int x IntList
BinTree = Int + BinTree x BinTree
Nat = Zero + Succ Nat
IntList = Nil * + Cons (Int x IntList)
BinTree = Leaf Int + Node (BinTree x BinTree)
Evaluation of variants
e ::= ... | c e*
Exp = ... | Con Ident (List Exp)
Val = ... | VCon Ident (List Val)
eval r (Con c e*) =
VCon c (eval* r e*)
Evaluation of variants II
e ::= ... | case e of p1 => e1 ; ... ; pn => en
p ::= _ | x | c p*
Exp = Case Exp (List (Pat x Exp))
Pat = PWild | PVar Ident | PCon Ident (List Pat)
Pattern Matching
Pattern Matching II
match : Pat -> Val -> String + Env
match PWild v =
Inr empty
match (PVar x) v =
Inr [ x := v ]
match (PCon c p*) (VCon d v*) | c == d =
let r* = match* p* v* in
combine r* -- merge bindings if disjoint, otherwise fail (linearity)
match _ _ =
Inl "pattern match failure"
Pattern Matching III
select : List (Pat x Exp) -> Val -> String + Env * Exp
select [] v =
inl "no suitable branch"
select ((p, e) : m*) v =
case (match p v) of
Inl s => select m* v
Inr r => Inr (r, e)
Evaluation of variants III
eval r (Case e m*) =
let v = eval r e in
case (select m* v) of
Inl message =>
error message
Inr (r', e') =>
let r'' = override r r' in
eval r'' e'
Recursive functions
e ::= ... | rec f(x) . e
Exp = ... | Rec Ident Ident Exp
Val = ... | VRec Env Ident Ident Exp
eval r (Rec f x e) =
VRec r f x e
eval r (App e1 e2) =
let fv@(VRec r' f x e) = eval r e1 in
let v = eval r e2 in
eval (put (put r' x v) f fv) e
Observations
Looking for a foundational metalanguage
Foundation: lambda calculus
e ::= x | \x.e | e e
Free and Bound Variables
FV(x) = { x }
FV(\x.e) = FV(e) \ { x }
FV(e1 e2) = FV(e1) U FV(e2)
BV(x) = { }
BV(\x.e) = BV(e) U { x }
BV(e1 e2) = BV(e1) U BV(e2)
Semantics of Lambda Calculus
\x.e -alpha-> \y.e[ x:=y ] if y not in FV(e)
(\x.e1) e2 -beta-> e1[ x := e2 ]
(\x.e x) -eta-> e if x not in FV(e)
Semantics of Lambda Calculus II
C ::= [] e | e [] | \x.[]
Semantics of Lambda Calculus III
Normal Form
A lambda expression is in (beta) normal form if no reduction applies.
e in normal form
if exists no e' such that e =beta=> e'
n is a normal form of e
if e =beta=>* n and n is normal form
Normal Form II
There are terms that have no normal form
(\x. x x) (\x. x x)
-beta->
(\x. x x) (\x. x x)
Theorem (Church-Rosser)
Suppose that e1 <=beta=>* e2.
Then there exists e' such that
e1 =beta=>* e' and
e2 =beta=>* e'
Corollary
A lambda expression has at most one normal form.
Modeling data types
Booleans
cond true e1 e2 = ...
Pairs
fst (pair e1 e2) = ...
Numbers: Church Numerals
ifzero (succ n) e1 e2 = ...
Recursion (Fixpoint Theorem)
Every lambda expression has a fixpoint.
That is, for each f there is an e such that
f e <=beta=>* e
Proof (Fixpoint Theorem)
Let e = Y f where
Y = \f . (\x.(f x x)) (\x.(f x x))
Wrapup
sufficient to simulate Kleene’s theory of recursive functions,
which in turn is sufficient to simulate Turing machines.
Hence, lambda calculus is a universal computing device (Turing equivalent)
Reduction Strategies
How to find the normal form of an expression?
Normal order reduction
each reduction step chooses the leftmost outermost redex
C ::= \x.e | [ ] e
Normalization theorem
If e ==>* n, then e ==>*no n
Reduction vs. Evaluation
Programming languages
They stop reducing at values
v ::= \x.e
more generally weak head normal forms (that may contain free variables)
Call-by-name and call-by-value
Controlling Evaluation
Continuation-passing style (CPS)
Continuation-Passing Style
Basic idea
Example (CPS)
fact n =
if n=0 then 1
else n * fact (n-1)
factc n k =
if n=0 then k 1
else factc (n-1) (\v. k (n*v))
CPS Interpreter
Scope:
Exp = Var Ident | Lam Ident Exp | App Exp Exp
| Cond Exp Exp Exp | Const Int | Add Exp Exp
Val = VInt Int | VBool Bool | VClos Ident Exp Env
The interpreter takes a continuation argument
Cont = Val -> Val
CPS Interpreter II
evalc : Env -> Exp -> Cont -> Val
evalc r (Var x) k =
k (get r x)
evalc r (Lam x e) k =
k (VClos x e r)
evalc r (App e1 e2) k =
evalc r e1 (\v1.
evalc r e2 (\v2.
match v1 with
(VClos x' e' r') =>
evalc (put r' x' v2) e' k))
CPS Interpreter III
evalc r (Cond e1 e2 e3) k =
evalc r e1 (\v1.
match v1 with
VBool b =>
if b then evalc r e2 k
else evalc r e3 k)
CPS Interpreter IV
evalc r (Const i) k =
k (VInt i)
evalc r (Add e1 e2) k =
evalc r e1 (\v1.
evalc r e2 (\v2.
match (v1, v2) with
(VInt i1, VInt i2) =>
k (VInt (i1+i2))))
Alternative: CPS Transformation
Instead of writing the interpreter in CPS:
Call-by-value CPS transformation
CV[[ x ]] = \k. k x
CV[[ \x.e ]] = \k. k (\x. CV[[ e ]])
CV[[ e1 e2 ]] = \k. CV[[ e1 ]] (\v1.
CV[[ e2 ]] (\v2.
v1 v2 k
"Fischer/Plotkin call-by-value CPS transformation"
Properties of Call-by-value CPS
CV[[ e ]](\x.x) =v=>* w iff
CV[[ e ]](\x.x) =n=>* w'
where w = w'
2. Simulation
e =v=>* \x.e' (using call-by-value evaluation)
iff
CV[[ e ]](\x.x) =v=>* \x. CV[[ e' ]]
Call-by-name CPS transformation
CN[[ x ]] = x
CN[[ \x.e ]] = \k. k (\x. CN[[ e ]])
CN[[ e1 e2 ]] = \k. CN[[ e1 ]] (\v1.
v1 (CN [[e2]]) k