CS 2135: Programming Language Concepts
Examples of Converting to Script Position

This page shows the solutions to the exercises we did in class on converting programs to script position.


(define (gscript x) (* x 6))
(define (hscript y) (+ y 5))
(+ 4 (gscript (hscript 3)))
[hint: convert to using h/web and gscript first, then to h/web and g/web]

Which do we move up first, hscript or gscript? Move up whichever should get executed first. Since hscript is the innermost call, it would get executed first, so we must move it into script position first.

(h/web 3 
       (lambda (box1)
         (+ 4 (gscript box1))))

Next move the call to gscript. Where do we move it to? We never move a call through a lambda, so we move it to just before the +.

(h/web 3 
       (lambda (box1)
         (g/web box1 
                (lambda (box2) 
                  (+ 4 box2)))))

Don't forget to rewrite gscript and hscript into g/web and h/web. Remember: to do that, we add a parameter "action" to each and send the result of the function to action:

(define (g/web x action) (action (* x 6)))
(define (h/web y action) (action (+ y 5)))

(output "The answer is "
	(+ (prompt-read-script "First number: ")
	   (prompt-read-script "Second number: ")))

In this program, think of output like printf; it's a generic function for producing output in some style of interface.

Here, we have two arguments to +; we need to decide which to process first. Let's request the first number first:

(prompt-read/web "First number: "
		 (lambda (box1)
		   (output "The answer is "
			   (+ box1
			      (prompt-read-script "Second number: ")))))

Now move up the request for the second number; remember, don't move past the lambda:

(prompt-read/web "First number: "
		 (lambda (box1)
		   (prompt-read/web "Second number: "
				    (lambda (box2)
				      (output "The answer is "
					      (+ box1 box2))))))

Let's look at something a bit harder:

(define (countdown n)
  (cond [(zero? n) (output "Liftoff!")]
	[else (begin
		(prompt-read-script (format "t - ~a and counting" n))
		(countdown (- n 1)))]))

(countdown (prompt-read-script "Time left on launch pad: "))

Think of this as a program for NASA: as countdown progresses, the program produces a progress report and expects the user to give some input to continue the countdown (the program ignores the content of the input though). The lower call to prompt-read-script is easy to move:

(prompt-read/web "Time left on launch pad: "
		 (lambda (box1)
		   (countdown box1)))

What about the call to prompt-read-script inside the body of countdown? Clearly it needs to move, but to where? Remember that in the first lecture on script position, we said that the answers of cond clauses are in script position. We'll therefore move it up to the front of the else clause:

(define (countdown n)
  (cond [(zero? n) (output "Liftoff!")]
	[else (prompt-read/web (format "t - ~a and counting" n)
			       (lambda (box1)
				 (begin
				   box1
				   (countdown (- n 1)))))]))

All calls to scripts are now in script position, so we're done.


We've looked at calls to scripts inside simple primitives (+) and calls to scripts inside conds (countdown). Finally, let's look at a call to a recursive script. This one asks a user to keep entering numbers at a prompt. When the user enters zero, the program prints the sum and stops.

(define (count-delay-script)
  (let ([new-delay (prompt-read-script "Delay: ")])
    (cond [(= 0 new-delay) 0]
	  [else (+ new-delay (count-delay-script))])))

(output "Total delay: " (count-delay-script))

As usual, the bottom call to output is easy to transform:

(count-delay/web (lambda (box1)
		   (output "Total delay: " box1)))

Now we have to rewrite count-delay-script to count-delay/web. Let's start with the header. As usual, we need to add a parameter action to the script to turn it into a web script:

(define (count-delay/web action)
  ...)

Let's insert the original body and call action on each answer in the cond clause. (In class, we wrapped the call to action around the entire let. It's actually better to move the call to action as far in as possible, because it prevents later errors in converting to script position. In this case, the cond is the last statement to execute, so we call action on each answer of the cond)

(define (count-delay/web action)
  (let ([new-delay (prompt-read-script "Delay: ")])
    (cond [(= 0 new-delay) (action 0)]
	  [else (action (+ new-delay (count-delay-script)))])))

Now, look for calls to move into script position. There are two: one to prompt-read-script and one to count-delay-script. Since the prompt-read-script call happens first, let's move it up first:

(define (count-delay/web action)
  (prompt-read/web
   "Delay: "
   (lambda (box1)
     (let ([new-delay box1])
       (cond [(= 0 new-delay) (action 0)]
	     [else (action (+ new-delay (count-delay-script)))])))))

Now, let's move the call to count-delay-script. It moves to the front of the else (since we don't move calls outside of answer position inside conds).

(define (count-delay/web action)
  (prompt-read/web
   "Delay: "
   (lambda (box1)
     (let ([new-delay box1])
       (cond [(= 0 new-delay) (action 0)]
	     [else
	      (count-delay/web
	       (lambda (box2)
		 (action (+ new-delay box2))))])))))

So, recursive programs aren't that special here -- they follow the same rules as we've used on all the previous conversions.


Exercises

  1. Hand evaluate a call to count-delay/web with the inputs 4, 5, and 0. Make sure you see how the program evaluates, what the various action arguments are, and how the final answer (9) is produced.

  2. Could you write the script-position transformer as a macro that operates over arbitrary code containing calls to scripts? Why or why not?


This page maintained by Kathi Fisler
Department of Computer Science Worcester Polytechnic Institute