0% found this document useful (0 votes)
41 views61 pages

6 Env

This document discusses implementing an interpreter for a simple functional programming language called Nano. It covers representing arithmetic expressions and adding support for variables and let bindings by using an environment to map variables to values. An example expression is evaluated step-by-step to demonstrate how the interpreter works.

Uploaded by

Apramay Singh
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
41 views61 pages

6 Env

This document discusses implementing an interpreter for a simple functional programming language called Nano. It covers representing arithmetic expressions and adding support for variables and let bindings by using an environment to map variables to values. An example expression is evaluated step-by-step to demonstrate how the interpreter works.

Uploaded by

Apramay Singh
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 61

CSE 114A

Foundations of Programming
Languages

Environments and closures

Based on course materials developed by Nadia Polikarpova and Owen Arden


Roadmap
Past weeks:

• How do we use a functional language?

Next weeks:

• How do we implement a functional language?


• … in a functional language (of course)

WHY??

• Master the concepts of functional languages by implementing them!


• Practice problem solving using Haskell

This week: Interpreter

• How do we evaluate a program given its abstract syntax tree (AST)?

2
The Nano Language
Features of Nano:

1. Arithmetic expressions
2. Variables and let-bindings
3. Functions
4. Recursion

3
Reminder: Calculator
Arithmetic expressions:
e ::= n
| e1 + e2
| e1 - e2
| e1 * e2

Example:
4 + 13
==> 17

4
Reminder: Calculator
Haskell datatype to represent arithmetic expressions:
data Expr = Num Int
| Add Expr Expr
| Sub Expr Expr
| Mul Expr Expr

Haskell function to evaluate an expression:


eval :: Expr -> Int
eval (Num n) = n
eval (Add e1 e2) = eval e1 + eval e2
eval (Sub e1 e2) = eval e1 - eval e2
eval (Mul e1 e2) = eval e1 * eval e2

5
Reminder: Calculator
Alternative representation:
data Binop = Add | Sub | Mul

data Expr = Num Int -- number


| Bin Binop Expr Expr -- binary expression

Evaluator for alternative representation:


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

6
The Nano Language
Features of Nano:

1. Arithmetic expressions [done]


2. Variables and let-bindings
3. Functions
4. Recursion

7
Extension: variables
Let’s add variables and let bindings!

e ::= n | x
| e1 + e2 | e1 - e2 | e1 * e2
| let x = e1 in e2

Example:
let x = 4 + 13 in -- 17
let y = 7 - 5 in -- 2
x * y

==> 34

8
Extension: variables
Haskell representation:
data Expr = Num Int -- number
| ??? -- variable
| Bin Binop Expr Expr -- binary expression
| ??? -- let expression

9
Extension: variables
type Id = String

data Expr = Num Int -- number


| Var Id -- variable
| Bin Binop Expr Expr -- binary expression
| Let Id Expr Expr -- let expression

10
Example Expression

Nano expression
let x = 1 in
let y = (let x = 2 in x) + x in
let x = 3 in
x + y
is represented in Haskell as:
exp1 = Let "x"
(Num 1) exp2

(Let "y"
exp3
(Add
(Let "x" (Num 2) (Var x))
exp4 (Var x))

(Let "x" exp5


(Num 3)
(Add (Var x) (Var y))))
11
Extension: variables

Haskell function to evaluate an expression:


eval :: Expr -> Int
eval (Num n) = n
eval (Var x) = ???
...

12
Extension: variables

How do we evaluate a variable?

We have to remember
which value it was bound to!

13
Environment
An expression is evaluated in an environment, which
maps the free variables of the expression to values

Examples:
• How should we represent the
x * y environment?
=[x:17, y:2]=> 34 • Which operations does it support?

x * y
=[x:17]=> Error: unbound variable y

x * (let y = 2 in y)
=[x:17]=> 34

14
Extension: variables

http://tiny.cc/cse116-vars-ind

15
Extension: variables

http://tiny.cc/cse116-vars-grp

16
Environment: API
To evaluate
let x = e1 in e2 in env:
• evaluate e2 in an extended environment env + [x:v]
• where v is the result of evaluating e1

To evaluate x in env:
• lookup the most recently added binding for x

type Value = Int

data Env = ... -- representation not that important

-- | Add a new binding


add :: Id -> Value -> Env -> Env

-- | Lookup the most recently added binding


lookup :: Id -> Env -> Value

17
Environment: Implementation
type Value = Int
type Id = String

type Env = [(Id,Value)]

-- | Add a new binding


add :: Id -> Value -> Env -> Env
add x v env = (x,v):env

-- | Lookup the most recently added binding


lookup :: Id -> Env -> Value
lookup x ((y,v):env) = if x == y then v else lookup x env

18
Evaluating expressions
Back to our expressions… now with environments!
data Expr = Num Int -- number
| Var Id -- variable
| Bin Binop Expr Expr -- binary expression
| Let Id Expr Expr -- let expression

19
Evaluating expressions
Haskell function to evaluate an expression:
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
20
Example evaluation
Nano expression
let x = 1 in
let y = (let x = 2 in x) + x in
let x = 3 in
x + y
is represented in Haskell as:
exp1 = Let "x"
(Num 1) exp2

(Let "y"
exp3
(Add
(Let "x" (Num 2) (Var x))
exp4 (Var x))

(Let "x" exp5


(Num 3)
(Add (Var x) (Var y))))
21
Example evaluation
eval [] exp1
=> eval [] (Let "x" (Num 1) exp2)
=> eval [("x",eval [] (Num 1))] exp2
=> eval [("x",1)]
(Let "y" (Add exp3 exp4) exp5)
=> eval [("y",(eval [("x",1)] (Add exp3 exp4))), ("x",1)]
exp5
=> eval [("y",(eval [("x",1)] (Let "x" (Num 2) (Var "x"))
+ eval [("x",1)] (Var "x"))), ("x",1)]
exp5
=> eval [("y",(eval [("x",2), ("x",1)] (Var "x") -- new binding for x
+ 1)), ("x",1)]
exp5
=> eval [("y",(2 -- use latest binding for x
+ 1)), ("x",1)]
exp5
=> eval [("y",3), ("x",1)]
(Let "x" (Num 3) (Add (Var "x") (Var "y")))
22
Example evaluation
=> eval [("y",3), ("x",1)]
(Let "x" (Num 3) (Add (Var "x") (Var “y")))
=> eval [("x",3), ("y",3), ("x",1)] -- new binding for x
(Add (Var "x") (Var "y"))
=> eval [("x",3), ("y",3), ("x",1)] (Var "x")
+ eval [("x",3), ("y",3), ("x",1)] (Var "y")
=> 3 + 3
=> 6

23
Example evaluation
Same evaluation in a simplified format (Haskell Expr terms replaced by their “pretty-
printed version”):
eval []
{let x = 1 in let y = (let x = 2 in x) + x in let x = 3 in x + y}
=> eval [x:(eval [] 1)]
{let y = (let x = 2 in x) + x in let x = 3 in x + y}
=> eval [x:1]
{let y = (let x = 2 in x) + x in let x = 3 in x + y}
=> eval [y:(eval [x:1] {(let x = 2 in x) + x}), x:1]
{let x = 3 in x + y}
=> eval [y:((eval [x:1] {let x = 2 in x}) + (eval [x:1] {x})), x:1]
{let x = 3 in x + y}
-- new binding for x:
=> eval [y:((eval [x:2,x:1] {x}) + (eval [x:1] {x})), x:1]
{let x = 3 in x + y}
-- use latest binding for x:
=> eval [y:( 2 + (eval [x:1] {x})), x:1]
{let x = 3 in x + y}
=> eval [y:( 2 + 1) , x:1]
{let x = 3 in x + y} 24
Example evaluation
=> eval [y:( 2 + 1) , x:1]
{let x = 3 in x + y}

=> eval [y:3, x:1] {let x = 3 in x + y}

-- new binding for x:


=> eval [x:3, y:3, x:1] {x + y}

=> (eval [x:3, y:3, x:1] x) + (eval [x:3, y:3, x:1] y)

-- use latest binding for x:


=> 3 + 3

=> 6

25
Can runtime errors occur?
Haskell function to evaluate an expression:
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

How do we make sure lookup doesn’t cause a run-time error?


26
Free vs bound variables
In eval env e, env must contain bindings for all free variables of e!

• an occurrence of x is bound if it is inside e2 where let x = e1 in e2


• an occurrence of x is free if it is not bound
• evaluation succeeds when an expression is closed (no free variables)

27
QUIZ

http://tiny.cc/cse116-free-ind
28
QUIZ

http://tiny.cc/cse116-free-grp
29
The Nano Language
Features of Nano:

1. Arithmetic expressions [done]


2. Variables and let-bindings [done]
3. Functions
4. Recursion

30
Extension: functions
Let’s add lambda abstraction and function application!
e ::= n | x
| e1 + e2 | e1 - e2 | e1 * e2
| let x = e1 in e2
| \x -> e -- abstraction
| e1 e2 -- application

Example:
let c = 42 in
let cTimes = \x -> c * x in
cTimes 2
==> 84

31
Extension: functions
Haskell representation:
data Expr = Num Int -- number
| Var Id -- variable
| Bin Binop Expr Expr -- binary expression
| Let Id Expr Expr -- let expression
| ??? -- abstraction
| ??? -- application

32
Extension: functions
Haskell representation:
data Expr = Num Int -- number
| Var Id -- variable
| Bin Binop Expr Expr -- binary expression
| Let Id Expr Expr -- let expression
| Lam Id Expr -- abstraction
| App Expr Expr -- application

33
Extension: functions
Example:
let c = 42 in
let cTimes = \x -> c * x in
cTimes 2

represented as:
Let "c"
(Num 42)
(Let "cTimes"
(Lam "x" (Mul (Var "c") (Var "x")))
(App (Var "cTimes") (Num 2)))

34
Extension: functions
Example:
let c = 42 in
let cTimes = \x -> c * x in
cTimes 2

How should we evaluate this expression?


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}

What is the value of cTimes???


35
Rethinking our values
Until now: a program evaluates to an integer (or fails)
type Value = Int

type Env = [(Id, Value)]

eval :: Env -> Expr -> Value

36
Rethinking our values
What do these programs evaluate to?
(1)
\x -> 2 * x
==> ???

(2)
let f = \x -> \y -> 2 * (x + y) in
f 5
==> ???

(1) evaluates to itself (not exactly, see later).

(2) evaluates to something like \y -> 2 * (5 + y)

37
Rethinking our values
Now: a program evaluates to an integer or a lambda abstraction (or fails)

• Remember: functions are first-class values

Let’s change our definition of values!


data Value = VNum Int
| VLam ??? -- What info do we need to store?

-- Other types stay the same


type Env = [(Id, Value)]

eval :: Env -> Expr -> Value

38
Function values
How should we represent a function value?
let c = 42 in
let cTimes = \x -> c * x in
cTimes 2
We need to store enough information about cTimes so that we can later evaluate
any application of cTimes (like cTimes 2)!

First attempt:
data Value = VNum Int
| VLam Id Expr -- formal + body

39
Function values
Let’s try this!
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}
-- evaluate the function:
=> eval [cTimes:(\x -> c*x), c:42]
{(\x -> c * x) 2}
-- evaluate the argument, bind to x, evaluate body:
=> eval [x:2, cTimes:(\x -> c*x), c:42]
{c * x}
=> 42 * 2
=> 84

Looks good… can you spot a problem?


40
QUIZ

http://tiny.cc/cse116-cscope-ind
41
QUIZ

http://tiny.cc/cse116-cscope-grp
42
Static vs Dynamic Scoping
What we want:
let c = 42 in
let cTimes = \x -> c * x in
let c = 5 in
cTimes 2
=> 84

Lexical (or static) scoping:

• each occurrence of a variable refers to the most recent binding in the


program text
• definition of each variable is unique and known statically
• good for readability and debugging: don’t have to figure out where a variable
got “assigned”

43
Static vs Dynamic Scoping
What we don’t want:
let c = 42 in
let cTimes = \x -> c * x in
let c = 5 in
cTimes 2
=> 10

Dynamic scoping:

• each occurrence of a variable refers to the most recent binding during


program execution
• can’t tell where a variable is defined just by looking at the function body
• nightmare for readability and debugging:

44
Static vs Dynamic Scoping
Dynamic scoping:

• each occurrence of a variable refers to the most recent binding during


program execution
• can’t tell where a variable is defined just by looking at the function body
• nightmare for readability and debugging:

let cTimes = \x -> c * x in


let c = 5 in
let res1 = cTimes 2 in -- ==> 10
let c = 10 in
let res2 = cTimes 2 in -- ==> 20!!!
res2 - res1

45
Function values
data Value = VNum Int
| VLam Id Expr -- formal + body
This representation can only implement dynamic scoping!

let c = 42 in
let cTimes = \x -> c * x in
let c = 5 in
cTimes 2
evaluates as:
eval []
{let c = 42 in let cTimes = \x -> c * x in let c = 5 in cTimes 2}

46
Function values
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:(\x -> c*x), c:42]
{let c = 5 in cTimes 2}
=> eval [c:5, cTimes:(\x -> c*x), c:42]
{cTimes 2}
=> eval [c:5, cTimes:(\x -> c*x), c:42]
{(\x -> c * x) 2}
=> eval [x:2, c:5, cTimes:(\x -> c*x), c:42]
{c * x}
-- latest binding for c is 5!
=> 5 * 2
=> 10

Lesson learned: need to remember what c was bound to when cTimes was
defined!
• i.e. remember the environment at function definition
47
Closures
To implement lexical scoping, we will represent function values as closures

Closure = lambda abstraction (formal + body) + environment at function definition


data Value = VNum Int
| VClos Env Id Expr -- env + formal + body

48
Closures
Our example:
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}
-- remember current env:
=> 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

49
QUIZ

http://tiny.cc/cse116-env-ind
50
QUIZ

http://tiny.cc/cse116-env-grp
51
Free vs bound variables
• An occurrence of x is bound if it’s inside
◦ e2 where let x = e1 in e2
◦ e where \x -> e
• An occurrence of x is free if it is not bound
• A closure environment has to save all free variables of a function definition!

let a = 20 in
let f =
\x -> let y = x + 1 in
let g = \z -> y + z in
a + g x -- a is the only free variable!
in ...

52
Evaluator
Let’s modify our evaluator to handle functions!
data Value = VNum Int
| VClos Env Id Expr -- env + formal + body

eval :: Env -> Expr -> Value


eval env (Num n) = VNum n -- 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 = ... -- as before
eval env (Let x e1 e2) = eval env' e2
where
v = eval env e1
env' = add x v env
eval env (Lam x body) = ??? -- construct a closure
eval env (App fun arg) = ??? -- eval fun, then arg, then apply
53
Evaluator
Evaluating functions:

• Construct a closure: save environment at function definition


• Apply a closure: restore saved environment, add formal, evaluate the body

eval :: Env -> Expr -> Value


...
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

54
Evaluator
Evaluating functions:

• Construct a closure: save environment at function definition


• Apply a closure: restore saved environment, add formal, evaluate the body

eval :: Env -> Expr -> Value


...
eval env (Lam x body) = VClos env x body
eval env (App fun arg) =
let vArg = eval env arg in -- eval argument
let (VClos closEnv x body) = (eval env fun) in
let bodyEnv = add x vArg closEnv in
eval bodyEnv body

55
Quiz

http://tiny.cc/cse116-enveval-ind

56
Quiz

http://tiny.cc/cse116-enveval-grp

57
Evaluator
eval []
{let f = \x -> x + y in let y = 10 in f 5}
=> eval [f:<[], \x -> x + y>]
{let y = 10 in f 5}
=> eval [y:10, f:<[], \x -> x + y>]
{f 5}
=> eval [y:10, f:<[], \x -> x + y>]
{<[], \x -> x + y> 5}
=> eval [x:5] -- env got replaced by closure env + formal!
{x + y} -- y is unbound!

58
Quiz

http://tiny.cc/cse116-enveval2-ind
59
Quiz

http://tiny.cc/cse116-enveval2-grp
60
Evaluator
eval []
{let f = \n -> n * f (n - 1) in f 5}
=> eval [f:<[], \n -> n * f (n - 1)>]
{f 5}
=> eval [f:<[], \n -> n * f (n - 1)>]
{<[], \n -> n * f (n - 1)> 5}
=> eval [n:5] -- env got replaced by closure env + formal!
{n * f (n - 1)} -- f is unbound!

Lesson learned: to support recursion, we need a different way of constructing the


closure environment!

61

You might also like