Environments & Closures¶
Intro¶
Quizzes
cse116-vars-ind -> E
cse116-free-ind -> B
cse116-cscope-ind -> B
cse116-env-ind -> A
cse116-enveval-ind -> D
cse116-enveval2-ind -> C
The Nano Language¶
Features of Nano:
1. Arithmetic expressions¶
Evaluator 1¶
e ::= n
| e1 + e2
| e1 - e2
| e1 * e2
-- haskell representation:
data Binop = Add | Sub | Mul
data Expr = Num Int
| Bin Binop Expr Expr
-- evaluator:
eval :: Expr -> Int
eval (Num n) = n
eval (Bin Add e1 e2) = eval e1 + eval e2
eval (Bin Sub e1 e2) = eval e1 - eval e2
eval (Bin Mul e1 e2) = eval e1 * eval e2
2. Variables and let-bindings¶
e ::= n | x
| e1 + e2 | e1 - e2 | e1 * e2
| let x = e1 in e2
-- haskell representation:
type Id = String
data Expr = Num Int -- number
| Var Id -- variable
| Bin Binop Expr Expr -- binary op
| Let Id Expr Expr -- let expr
thus, expressions must be evaluated in Environments
Evaluator 2¶
Previous implementation: Evaluator 1
type Value = Int
data Env = ...
-- add new id/value to env
add :: Id -> Value -> Env -> Env
-- lookup id in env
lookup :: Id -> Env -> Value
-- evaluator:
eval :: Env -> Expr -> Value
eval env (Num n) = n
eval env (Var x) = lookup x env
eval env (Bin op e1 e2) = f v1 v2
where
v1 = eval env e1
v2 = eval env e2
f = case op of
Add -> (+)
Sub -> (-)
Mul -> (*)
eval env (Let x e1 e2) = eval env' e2
where
v = eval env e1
env' = add x v env
Runtime Errors¶
Lookups can fail when a var is not bound!
How do we ensure that it doesn’t raise a runtime error?
In eval env e
, env must contain bindings for all free vars of e. Evaluation only succeeds when all expressions are closed.
3. Functions¶
Let’s add lambda abstractions and function application!
e ::= n | x
| e1 + e2 | e1 - e2 | e1 * e2
| let x = e1 in e2
| \x -> e -- abstraction
| e1 e2 -- application
-- haskell representation:
data Expr = Num Int -- number
| Var Id -- variable
| Bin Binop Expr Expr -- binary op
| Let Id Expr Expr -- let expr
| Lam Id Expr -- abstraction
| App Expr Expr -- application
Note
Now, let’s try to evaluate something…
eval [] {let c = 42 in let cTimes = \x -> c * x in cTimes 2}
=> eval [c:42] {let cTimes = \x -> c * x in cTimes 2}
=> eval [cTimes:???, c:42] {cTimes 2}
How do we represent lambdas as a value? Let’s try data Value = VNum Int | VLam Id Expr
and evaluate…
eval [] {let c = 42 in let cTimes = \x -> c * x in cTimes 2}
=> eval [c:42] {let cTimes = \x -> c * x in cTimes 2}
=> eval [cTimes:(\x -> c * x), c:42] {cTimes 2}
=> eval [cTimes:(\x -> c * x), c:42] {(\x -> c * x) 2}
=> eval [x:2, cTimes:(\x -> c * x), c:42] {x * c}
=> 42 * 2
=> 84
But what if c is redefined before cTimes is used?
The problem that this brings up is static v. dynamic scoping; static scoping = most recent binding in text, whereas dynamic = most recent binding in execution
How do we implement lexical scoping? See Closures
Now let’s update our evaluator! Previous implementation: Evaluator 2
Evaluator 3¶
data Value = VNum Int -- new!
| VClos Env Id Expr -- env + formal + body
eval :: Env -> Expr -> Value
eval env (Num n) = VNum n -- we must wrap in VNum now!
eval env (Var x) = lookup x env
eval env (Bin op e1 e2) = VNum (f v1 v2)
where
(VNum v1) = eval env e1
(VNum v2) = eval env e2
f = case op of
Add -> (+)
Sub -> (-)
Mul -> (*)
eval env (Let x e1 e2) = eval env' e2
where
v = eval env e1
env' = add x v env
-- new!
eval env (Lam x body) = VClos env x body
eval env (App fun arg) = eval bodyEnv body
where
(VClos closEnv x body) = eval env fun -- eval function to closure
vArg = eval env arg -- eval argument
bodyEnv = add x vArg closEnv
But note: this evaluator doesn’t cover recursion!
4. Recursion¶
We have to do this in homework, yay! See hw4.
Environments¶
an environment maps all free vars to values
x * y
=[x:17, y:2]=> 34
x * y
=[x:17]=> Error: unbound var y
x * (let y = 2 in y)
=[x:17]=> 34
To evaluate let x = e1 in e2
in env
:
evaluate
e2
in an extended envenv + [x:v]
where
v = eval e1
Closures¶
Closure = lambda abstraction (formal + body) + environment at function definition
a closure environment must save all free variables of a function defn!
data Value = VNum Int
| VClos Env Id Expr -- env + formal + body
-- our syntax:
-- binding:<env, lambda>
-- now, eval:
eval [] {let c = 42 in let cTimes = \x -> c * x in let c = 5 in cTimes 2}
=> eval [c:42] {let cTimes = \x -> c * x in let c = 5 in cTimes 2}
=> eval [cTimes:<[c:42], \x -> c * x>, c:42] {let c = 5 in cTimes 2}
=> eval [c:5, cTimes:<[c:42], \x -> c * x>, c:42] {cTimes 2}
=> eval [c:5, cTimes:<[c:42], \x -> c * x>, c:42] {<[c:42], \x -> c * x> 2}
-- restore env to the one inside the closure, then bind 2 to x:
=> eval [x:2, c:42] {c * x}
=> 42 * 2
=> 84