• No results found

Further Reading

Exercise 3. 18 [ ] Add an expression to the defined language:

3.5 Procedures.

So far our language has only the primitive operations that were included in the original language. For our interpreted language to be at all useful, we must allow new procedures to be created. We use the following syntax for procedure creation and application:

Thus we can write programs like

let f = proc (y, z) +(y,-(z,5))in (f 2 28)

Since the proc form may be used anywhere an expression is allowed, we can also write (proc

(y,z) + (y, - (z,5)) 2 28). This is the application of the procedure proc (y, z)

+ (y, - (z,5)) to the literals 2 and 28.

We wish procedures to be first-class values in our language. Thus we want

information must be included in a value representing a procedure. To do this, we consider what happens at procedure-application time.

When a procedure is applied, its body is evaluated in an environment that binds the formal parameters of the procedure to the arguments of the application. Variables occurring free in the procedure should also obey the lexical binding rule. This requires that they retain the bindings that were in force at the time the procedure was created. Consider the following example:

let x = 5in let f = proc (y, z) +(y,-(z,x)) x = 28 in (f 2 x)

When f is called, its body should be evaluated in an environment that binds y to 2, z to 28, and x

to 5. Recall that the scope of the inner declaration of x does not include the procedure declaration. Thus from the position of the reference to x in the procedure's body, the nearest lexically

enclosing declaration of x is the outer declaration, which associates x with 5.

In order for a procedure to retain the bindings that its free variables had at the time it was created, it must be a closed package, independent of the environment in which it is used. Such a package is called a closure. In order to be self-contained, a closure must contain the procedure body, the list of formal parameters, and the bindings of its free variables. It is convenient to store the entire creation environment, rather than just the bindings of the free variables, but see exercise 3.27 for an alternative. We sometimes say the procedure is closed over or closed in its creation

environment.

We can think of ProcVal as a data type; the interface consists of closure, which tells how to build a procedure value, and apply-procval, which tells how to apply a procedure value. When a procedure is applied, its body is evaluated in an environment that binds the formal parameters of the procedure to the arguments of the application. Therefore these procedures should satisfy the condition

(apply-procval (closure ids body env) args) = (eval- expression body (extend-env ids args env))

According to the methodology described in section 2.3.2, we can employ a procedural representation for procedures by defining closure to have a value that is a procedure that expects an argument list.

(define closure (lambda (ids body env) (lambda (args) (eval- expression body (extend-env ids args env)))))

(define apply-procval (lambda (proc args) (proc args)))

Alternatively, since closures are the only kind of procedure values in our language, we can define ProcVal as an abstract syntax tree representation by writing

(define-datatype procval procval? (closure (ids (list- of symbol?)) (body expression?) (env environment?)))

In the abstract syntax tree representation for procedures, apply-procval uses cases to take the closure apart and then invokes the body of the closure in the appropriately extended environment:

(define apply-

procval (lambda (proc args) (cases procval proc (closure (ids body env) (eval- expression body (extend-env ids args env))))))

Now we can see how to modify eval-expression to handle programmer-defined procedures. This client code manipulates procedures only through the ProcVal interface, so it is independent of the representation of procedures. When a proc expression is evaluated, all that is done is to build a closure and return it immediately.

(define eval-expression (lambda (exp env) (cases expression exp| (proc- exp (ids body) (closure ids body env)) ...)))

The body of the procedure is not evaluated here: it cannot be evaluated until the values of the formal parameters are known, when the closure is applied to some arguments.

When an application is evaluated, the operator and the operands are evaluated, and the results are sent to apply- procval, which knows about the representation of procedures:

The operands are also called the actual parameters. These are expressions, and should not be confused with their values, which we consistently call the arguments to the procedure, nor should they be confused with the bound variables or formal parameters of the procedure that will be bound to them.

The interpreter is shown in figure 3.7. To see how all this fits together, let us consider a simple calculation. In this calculation, we write «exp» to denote the abstract syntax tree associated with the expression exp, and we write [x=a, y=b] env in place of (extend-

env '(x y) '(a b) env).

(eval-

expression <<let x = 5 in let x = 38 f = proc (y, z) * (y,+(x,z)) g = proc (u) +(u,x) in (f (g 3) 17) >> env0)= bind x and evaluate the body of thelet(eval-

expression <<let x = 38 f = proc (y, z) *(y,+(x,

z)) g = proc (u) +(u,x) in (f (g 3) 17)

>> env1) whereenv1 = [x = 5] env0= bind x, f, andgand evaluate the body of thelet(eval- expression <<(f (g 3) 17)>> env2) whereenv2 = [x = 38, f = (closure (y z) <<* (y, +(x,z))>> env1), g = (closure (u) <<+(u,x)>> env1) ]env1= rule forapp-expin

eval-expression(let ((proc (eval-expression <<f>> env2)) (args (eval-rands '(<<(g 3) >> <<17>>) env2))) (apply-procval proc args))

Figure 3.7 Interpreter with user-defined procedures

Before finishing this calculation, let us work on (g 3) in env2:

(eval-expression <<(g 3)>> env2)= rule for app-exp in eval-expression

(let ((proc (eval-expression <<g>> env2)) (args (eval-

rands '(<<3>>) env2))) (apply-procval proc args))= evaluate the rator

and the rands(let ((proc '(closure (u) <<+(u,x)

>> env1)) (args '(3))) (apply-procval proc args))= substitute the

values of proc and args(apply-procval '(closure (u) <<+(u,x) >> env1) '(3))

= definition ofapply-procval(eval-expression <<+(u,x)>> [u = 3] env1)= 3 + 5 = 8

Now we can finish the main calculation:

(let ((proc '(closure (y z) <<*(y,+(x,z))

>> env1)) (args '(8 17))) (apply-procval proc args))= substitute the values ofproc

andargs(apply-procval '(closure (y z) <<*(y,+(x,z))>> env1) '(8 17))= definition ofapply-procval(eval-expression <<*(y,+(x,z))>> [y = 8, z = 17] env1)

= 8 * (5 + 17) = 8 * 22 = 176