Let's get Functional!

Chapter: Let's get Functional!

No language would be complete without the ability to create new procedures. Our new language will implement the lambda expression.

There are a few questions we need to answer before we can go about implementing lambda expressions.

Q. 3
When a lambda expression is used in an application, what happens? For instance, what does an interpreter do when it comes across ( (lambda (x y) (* x y)) 2 4) ?


Q. 4
Is the method used in the previous question sufficient to handle all situations?


Q. 5
Why not?


Q. 6
So what should the value of a lambda expression be?


An easy way to represent closures is as a variant of procval. We add a variant tagged closure to our definition of procval:

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

Now we need to modify apply-proc so that it recognizes closures.

(define apply-proc
  (lambda (proc args)
    (cases procval proc
	(prim-proc (prim-op) (apply-prim-op prim-op args))
	(closure (formals body env)
	   (eval-exp body (extend-env formals args env)))
	(else (error 'apply-proc "Invalid procedure: ~s" proc)))))

We are ready for Mini-Scheme-J. The syntax is extended once more, this time to include lambda expressions. Here is the grammar for Mini-Scheme-J.

<exp> ::= <number>			lit-exp (datum)
        | <varref>			varref-exp (var)
	| (if <exp> <exp> <exp>)	if-exp (test-exp then-exp else-exp)
	| (let (<decls>) <exp>)		let-exp (ids rands body)
        | (lambda ({<id>}*) <exp>)	lambda-exp (formals body)
        | (<exp> {<exp>}*)		app-exp (rator rands)
	
<decls> ::= <empty>
        | (<id> <exp>) <decls>

Parsing (lambda (x y) (+ x y)), for example, produces (in an extended version of structure-of)

#(struct:lambda-exp
  (x y)
  #(struct:app-exp #(struct:varref-exp +) (#(struct:varref-exp x) #(struct:varref-exp y))))
You must also adjust the parser so that the rator field of an app-exp will accept an arbitrary expression.

Now when a lambda expression is evaluated we need to return a closure. The line we need to add to eval-exp is simply:

(define eval-exp
  (lambda (exp env)
    (cases expression exp
	       .....
	    (lambda-exp (formals body)
 	       (closure formals body env))

	       ....)))


Exercise 3

1. Implement Mini-Scheme-J. Almost all of the code needed to update your interpreter can be found in this section (above). All you need to do in addition is update the parser.

> (read-eval-print)
MS> ((lambda (x) x) 1)
1
MS> ((lambda (x y) (* x y)) 2 4)
8
MS> (let ((sqr (lambda (x) (* x x))))
        (sqr 64))
4096
MS> (let ((sqr (lambda (x) (* x x))))
        (let ((cube (lambda (x) (* x (sqr x)))))
        (cube 3)))
27



The let keyword you implemented earlier is a good example of what is known as syntactic sugar. That is, it could have been implemented in terms of other language features, rather than as a new language feature.

Q. 7
How could we have defined let using other language features?


So, we didn't even need to add let as a new language feature. We could have simply instructed our parser how to translate let expressions into lambda expressions! This is a good lesson. Before implementing a new language feauture, one should always be sure that one hasn't already implemented it. : )


rms@cs.oberlin.edu