Given the confusion that existed at the end of Tuesday's class, this lab will have you work on a related yet separate problem from what we were doing in class on Tuesday. We will clarify the material from the end of Tuesday's class on Thursday.
This lab will have you write a simple compiler for one stage in the process of converting regular programs to web scripts. It is designed to give you practice implementing compilers (programs which transform programs to other programs).
We have been working on CPS because it shows us how to transform standard (tree-like) programs into programs in which all calls to functions are in tail position. The motivation for this is that web scripts are programs in which all calls to functions are in tail position.
Consider what CPS has bought us. Initially, we had a textual pizza program that looked like:
(display-order (prompt-read-many1 promptlist1) (prompt-read-many2 promptlist2))
If we CPSed this program, we would get:
(prompt-read-many1/k promptlist1 (lambda (answers1) (prompt-read-many2/k promptlist2 (lambda (answers2) (display-order answers1 answers2)))))
This says that prompt-read-many1/k will do its usual processing, then call (lambda (answers1) ...) to continue the program.
In contrast, the web program looked like:
(define (prompt-name-addr-page) ... (prompt-order-script name addr)) (define (prompt-order-script name addr) ... (display-order-script name addr size style toppings)) (define (display-order-script name addr size style toppings) ...)
What's the difference between these two versions? The web version doesn't have any anonymous functions! Each continuation [(lambda (answers1) ...) and (lambda (answers2) ...) in the CPSed version] is an explicitly-named function (aka a script) in the web version. In particular, prompt-order-script is (lambda (answers1) ...) and display-order-script is (lambda (answers2) ...). Think about how you wrote your HTML forms: in the ACTION item, you gave the name of a script to call, not a lambda expression.
So, if we want to convert the CPSed version into a true set of scripts, we need to give an explicit name to each lambda expression and replace turn each use of a lambda with its new explicit name. Thus, our CPSed version should become:
(prompt-read-many1/k promptlist1 new-prompt-order-script) (define (new-prompt-order-script answers1) (prompt-read-many2/k promptlist2 new-display-order-script)) (define (new-display-order-script answers2) (display-order answers1 answers2))
This process is called "lambda lifting", since you "lift" lambdas to the top level and give them names. This lab asks you to implement a lambda lifter.
Develop a function lambda-lift which consumes and expr and returns two pieces of information: a new version of the expr (with no proc statements in it) and a new list of defs (make-defs) that give names to the procs that were in expr.
lambda-lift : expr -> expr and list-of-defs
Here are some examples of what lambda-lift should return:
(lambda-lift 4) ==> 4 and empty
(lambda-lift (make-plus 4 5)) ==> (make-plus 4 5) and empty
(lambda-lift (make-plus 4 (make-apply (make-proc 'x (make-var 'x)) 5))) ==> (make-plus 4 (make-apply (make-var 'newproc) 5)) and (list (make-def 'newproc (make-proc 'x (make-var 'x))))
(lambda-lift (make-plus (make-apply (make-proc 'y (make-plus (make-var 'y) 7)) 6) (make-apply (make-proc 'x (make-var 'x)) 5))) ==> (make-plus (make-apply (make-var 'newproc1) 6) (make-apply (make-var 'newproc2) 5)) and (list (make-def 'newproc1 (make-proc 'y (make-plus (make-var 'y) 7))) (make-def 'newproc2 (make-proc 'x (make-var 'x))))
Implement this for the number?, var?, plus?, proc?, and apply? cases.
You need to know about two new Scheme features to implement this function:
To generate a new name (and you need a new name for each proc), use the function gensym; it takes no arguments and returns a new symbol (name). Try it at a DrScheme prompt to see how it works.
To return multiple values from a function, you need to use the values construct. Here's a simple example:
(define (square-n-cube x) (values (* x x) (* x x x)))
This function returns two answers: x squared AND x cubed. When you call this function, you need to indicate that you expect two values. We do this with let-values:
(let-values ([(squarex cubex) (square-n-cube 2)]) ...)
In the rest of the let-values expression, squarex will have value 4 and cubex will have value 8.
Hints: Start simple! Follow the template for expr. Ask yourself what each call to lambda-lift returns. Use the examples above to help you develop the program.
Consider our original CPSed program from above (with (lambda (answers1) ...) and (lambda (answers2) ...) and the result (also shown above) after lambda-lifting this program. Remember that a compiler must never change the final answer that a program produces. Ask yourself what would happen if you ran the lifted version of the CPS program. Would it run properly? If not, why not and what do you need to do to fix the problem? [Hint: something does go wrong ...]