Review – TINY interpreter ev
ev takes as input an AST of an expression e and an environment env, and returns an AST of a value.
(define ev
(lambda (e env) (cond
((eq? (car e) ’&const) ;; e=(&const c) e)
((eq? (car e) ’&var) ;; e=(&var v) (lookup env (cadr e)))
((eq? (car e) ’&lambda) ;; e=(&lambda parms body) (mk-closure env e))
((eq? (car e) ’&if)
(let ((a (cadr e)) ;; e=(&if a b c) (b (caddr e))
(c (cadddr e)))
(ev (if (equal? (ev a env) ’(&const false)) c b) env)))
((eq? (car e) ‘&apply) ;; e=(&apply f args) (let*((f (cadr e))
(args (caddr e)) (fv (ev f env))
(av (map (lambda (a) (ev a env)) args))) (if (and (pair? fv) (eq? (car fv) ’&const))
(delta fv av)
TINY interpreter ev — basic structure Note:
Just by looking at the function ev we do not know how environments or closures are implemented. ev is said to be representation independent.
TINY interpreter ev — environments
procedural (functional) representation
(define extend
(lambda (env x v) (lambda (y)
(if (eq? x y) v
(lookup env y))))) (define lookup
(lambda (env y) (env y))) (define extend*
(lambda (env xs vs) (if (null? xs)
env
(extend* (extend env (car xs) (car vs)) (cdr xs)
TINY interpreter ev — closures list (data) representation
(define mk-closure ;; returns (&closure env parm-list body) (lambda (env v)
(cond
((eq? (car v) ’&lambda)
‘(&closure ,env ,(cadr v) ,(caddr v)))))) (define apply-cl
(lambda (vf va) (cond
((eq? (car vf) ’&closure)
(let ((env (cadr vf)) ;; environment
(p (caddr vf)) ;; parameter list
(b (cadddr vf))) ;; body
(if (= (length p) (length va)) (ev b (extend* env p va))
TINY interpreter ev — delta
delta is needed to apply a functional constant (our +, −,∗, / operations) to a value. ev uses the
corresponding Scheme operations. If a function symbol such as “+” is encountered, it is looked up in the
environment and the list ‘(&const hf unction+i) is returned. Note that an application of such a function requires all arguments to be constants, i.e., of the form ‘(&const . . .).
(define delta (lambda (f a)
(let ((R (lambda (s) ‘(&const ,s))) (R-1 (lambda (cl)
(cond
((eq? (car cl) ‘&const) (cadr cl))
(else
(error ’delta "non-const args")))))) (R (apply (R-1 f) (map R-1 a))))))
(define interpret-free-var (lambda (x)
‘(&const ,(eval x))))
Call-by-value closure interpreter evaluate
That’s it! We are now ready to put everything together.
(define evaluate (lambda (m)
(unparse (ev (parse m) empty-env))))
Questions
• What parameter passing style does our interpreter use?
• What is the order of evaluation of the actual parameters for a function application?
Call-by-name closure interpreter
How do we have to modify our interpreter to implement call–by–name instead of call–by–value?
Key idea:
If the argument of an application is not a value, we can postpone its evaluation by wrapping it into a closure that treats the argument expression as a lambda
abstraction without parameters. Such a “special”
closure is called a thunk. If we need the “real” value of an argument expression, we just evaluate the thunk in its environment.
Note that now our environment can contain three types of values, namely constants, closures, and thunks.
Dynamically-scoped interpreter
How do we have to modify our interpreter to implement call-by-value and dynamic scoping?
Key idea:
The difference between static and dynamic scoping in TINY is with respect to the rules how closures are applied.
• static ⇒ use environment within the closure
• dynamic ⇒ ignore environment within the closure and use current environment at the application (remember capturing!).
ev[ ((lambda (x) ((lambda (z)
((lambda(x) (z x)) 3))
(lambda (y) (+ x y)) )) 1)]
⇒
ev[ ((lambda (x)
((lambda(x) ((lambda (y) (+ x y)) x)) 3))
Closure interpreters
• Interpreters are important tools to understand and specify the semantics of programming languages. • Closures are an important concept for any language
with functions as first order objects and static
scoping. It gives us an idea what “price” we have to pay if we want first–order functions or a particular parameter passing style.
• So far, we only talked about “pure” functional
languages without a store and assignment (no side effects). How to extend our interpreters to deal with a store?
– Introduce a store as a function from addresses to values: store = Memory Locations → V alues
– Modify environments to map names to memory locations: ρ ∈ Env = V ariables → Memory Locations
– Modify ev to take one more argument: (define ev (lambda (e env store) . . .)). In addition, ev will return a value and a store. – Of course, there is a lot more work to be done
Part IV: Procedural Language Issues Procedures: A Control Abstraction
• A block of code that can be called (imperative)
• A lambda expression (functional)
• A clause (logic programming)
Components of a Procedure
1. Name
2. Formal parameters, optionally with types • parameter (formal parameter)
Local variable whose value is received from caller • argument (actual parameter)
The info passed from caller to callee
3. Body, which is a syntactic construct in the language: • Block, i.e., declarations and statements
(imperative)
• Expression (functional)
• Conjunction of terms (logic programming)
Procedure Implementation Issues
The general notion of a procedure leaves a number of points unspecified:
• How to pass parameters when the procedure is called • How to maintain local state and control information • How to access non-local names within a procedure
Parameter Passing
Matching arguments with parameters: 1. Positional association:
• Arguments are associated with parameters left to right
2. Keyword association:
• Arguments are given tags, e.g.,
procedure plot (x,y: in real; penup: in boolean)
...
plot(0.0, 0.0, penup=>true)
plot(penup=>true, x=>0.0, y=>0.0)
3. Optional arguments or different number of arguments:
• E.g., C printf(. . . )
• Extra arguments are packaged into some structure
Passing Modes
How to treat arguments (pass-by-x/call-by-x): 1. Pass by value
(C, C++, Pascal, Ada, Scheme, Algol68) 2. Pass by value-result
(Ada)
3. Pass by reference
(C++ with &, Fortran77/90, Pascal with var, COBOL)
4. Pass by name (Algol 60)
Example for Passing Modes
{ c : array[1..10] of integer; m,n integer;
procedure r (i , j : integer ) begin i := i + 1; j := j + 2 end r; ... m := 2; n := 3; r(m,n); // call 1 write m , n ; // print 1 m := 2; c[1] := 1; c[2] := 4; c[3] := 8; r(m,c[m]); // call 2 write m , c[m]; // print 2 }
Pass by Value
• Initial values of parameters copied from current values of arguments
• Final values of parameters are “thrown away” • Example: call 1: i = 2 j = 3 print 1: call 2: i = 2 j = 4 print 2: Discussion:
+ Arguments protected from changes in procedure – Requires copying of values: costs time and space,
Pass by Value-Result (Copy-In / Copy-Out) • Initial values of parameters copied from current
values of arguments
• Final values of parameters copied back to arguments • call 1:
– initial: i = 2 j = 3 – final: i = 3 j = 5
– return: m and n set to 3 and 5 • print 1:
• call 2:
– initial: i = 2 j = 4 – final: i = 3 j = 6
– return: which element of c is modified, c[2] or
c[3]? • print 2:
– if c[2] is modified: – if c[3] is modified:
Problem with Pass by Value-Result Calls Order of assignments and address computations is important.
Options:
1. Perform return address computations at call time: On return from call 2:
m set to 3; c[2] set to 6 print 2:
2. Perform return address computations at return time: On return from call 2:
m set to 3; c[3] set to 6 print 2:
Pass by Reference
• Parameters are pointers to the “arguments”.
• Address computations are performed at procedure call.
• Changes to the parameters inside procedure body are thus changes to the “arguments”
Example: • call 1:
– initial: i = 2 j = 3 – final: i = 3 j = 5 – return: m set to 3, n set to 5 • print 1:
• call 2:
– initial: i = 2 j = 4 – final: i = 3 j = 6
– return: m set to 3, c[2] set to 6 • print 2:
Pass by Reference
• Benefit: No copying for variables
• Problem: allow redefinition of expressions and constants?
• Problem: Leads to aliasing
– two or more visible names for same location – can cause to side effects not visible from code
itself
Question: What if actual parameter is an expression such as 2+3?
Aliasing
{ y : integer ;
procedure p ( x : integer ) begin x := x + 1; x := x + y end p; ... y := 2; p(y); write y } Pass by Reference:
• The identifiers x and y refer to the same location in call of p
• Result of “write y”? Pass by Value-Result:
• The identifiers x and y refer to different locations in call of p
More Aliasing
{ i, j, k : integer ;
procedure q ( a, b : integer ) begin a := i * b; b := i * b; end q; ... i := 2; j := 3; k := 4; q(i,j); q(k,k); }
• First call has global-formal aliases: – a and i
– b and j
• Second call has formal-formal alias: – a and b
Pass by Name
• A “name” for the argument expression is passed into procedure. Note: the expression is not evaluated. • Like textual substitution of argument in procedure • Thus expression evaluations are done whenever
parameter is used
• Example:
– call 1: m set to 3, n set to 5 – print 1:
– call 2: m set to 3, but then c[3] set to 10 – print 2:
Discussion
• Benefits: same as pass by reference • Problems: Inefficient, requires a thunk:
– essentially a little program that represents argument is passed
Things to do
• Read Sethi Chs. 5.3 – 5.7