CS 2135: Programming Language Concepts
Notes on Script Position

This page summarizes the lecture on script position and how to convert programs to use script position.


Introduction to Script Position

A function call is in script-position in an expression if the value returned from the call is the value of the entire expression (in other words, if the whole computation terminates immediately after the function call). Examples:


Why Does Script Position Matter?

When we discussed web programming, we said that the difference between textual I/O programs and their corresponding web/script versions is that the textual versions are "tree-like" (see the calls to prompt-read-many nested inside the calls to display-routes), while the script version is "linear" (each script function ended with a function call). Script-position calls distinguish "tree-like" from "linear" programs. In a linear program (like web scripts) all calls to user-defined functions must be in script position.

Thus, to translate tree-like programs into linear programs, we need to be able to identify and remove calls that aren't in script position.

To see the impact of script position, consider the following two versions of sum (over a list of numbers):

(define (sum alon)
  (cond [(empty? alon) 0]
        [(cons? alon) (+ (first alon) (sum (rest alon)))]))

(define (sum2 alon)
  (sum-help alon 0))

(define (sum-help alon total)
  (cond [(empty? alon) total]
        [(cons? alon) (sum-help (rest alon) (+ (first alon) total))]))

Sum and sum2 return the same answers, but the shape of their computations is somewhat different. Consider the major steps when running each program on the input (list 1 3 5 7):

(sum (list 1 3 5 7))
(+ 1 (sum (list 3 5 7)))
(+ 1 (+ 3 (sum (list 5 7))))
(+ 1 (+ 3 (+ 5 (sum (list 7)))))
(+ 1 (+ 3 (+ 5 (+ 7 (sum empty)))))
(+ 1 (+ 3 (+ 5 (+ 7 0))))
(+ 1 (+ 3 (+ 5 7)))
(+ 1 (+ 3 12))
(+ 1 15)
16

(sum2 (list 1 3 5 7))
(sum-help (list 1 3 5 7) 0)
(sum-help (list 3 5 7) 1)
(sum-help (list 5 7) 4)
(sum-help (list 7) 9)
(sum-help empty 16)
16

Notice how the sum version builds up a list of pending computations waiting for the successive results of sum, while no computations are waiting for the results of the calls to sum-help. In other words, the calls to sum build up in an arrow-like shape; the sum-help calls, in contrast, all line-up underneath each other). The sum-help calls are all in script position; the sum calls are not.

This difference also has an efficiency implication. Pending computations have to be stored somewhere (they are stored on the stack). Thus, the sum version uses stack storage (plus some extra time to manage the stack storage) that the sum-help version doesn't need. If we ran these two programs on a long-enough list, we could run out of memory using the sum version, while not running out of memory with the sum-help version.


How Do We Move Calls into Script Position?

Fortunately, we can move calls that are not in script position into script position through a fairly straightforward method. We'll demo the method first, then summarize the steps. Note that this method preserves the behavior of the original program (as well it should!).

In the following examples, we'll use the word "script" in the name of scripts. We want to move all calls to scripts into script position.

Consider the following definition and expression:

(define (fscript x) (+ x 3))
(* (fscript 5) 2)

The call (f 5) is not in script position. If we wanted to put it in script position, we'd have to move it outside of the multiplication. Doing so, though, would leave a hole in the multiplication expression, as follows:

(* hole 2)

Having the unbound variable "hole" in the expression is bad though, since unbound variables lead to errors. We can fix this problem by adding (lambda (hole) ...) to the expression:

(lambda (hole) (* hole 2))

Now return to the call to fscript that we yanked out. To put the call in script position, we can't embed it in another expression. So, the call to fscript must be at the top level:

(fscript 5)

This isn't quite right though, because this expression would return 8 where the original expression returned 16. We need to send the return value from this call to our new (lambda (hole) (* hole 2)) expression, but while leaving the call to fscript at the top level. We can do this by passing the lambda expression to fscript as an additional argument and having fscript send its result to the lambda expression, as follows. To avoid confusion, I'll rename fscript to f/web. The new argument to f/web is called "action" because it corresponds to the action part of a CGI script.

(define (f/web x action) (action (+ x 3)))
(f/web 5 (lambda (hole) (* hole 2)))

The new call to f/web returns the same answer as the original call to f, but note that the call to f/web is in script-position, while the call to fscript is not.

This new (lambda (hole) ...) expression captures the pending computation for the call to fscript. Pending computation functions are called continuations (because they show how to "continue" the computation).

Summary of the method

To move a call to function fscript into script position in expression E:

Example: (define (fscript x) (+ x 3))
         (* (fscript 5) 2)
  1. Copy the original call to fscript to the top-level (ie, not nested inside any other expression)

    (fscript 5)
  2. Replace the entire call to fscript in E with a new variable.

    (* hole 2)
  3. Enclose the revised E in a lambda expression that has the new variable as a parameter. For example, assuming the variable used in the previous step was named "hole", wrap the revised E in (lambda (hole) ...).

    (lambda (hole) (* hole 2))
  4. Create a new version of fscript (here called f/web) that takes an additional argument (contract: a function of one argument) and calls that function on the original body of fscript.

    (define (f/web x action) (action (+ x 3)))
    
  5. Modify the copied call to fscript to call f/web, passing the (lambda (hole) ...) function as the last argument [assuming you haven't already done this -- a function never needs more than one continuation argument].

    (f/web 5 (lambda (hole) (* hole 2)))
    
  6. Make sure that all calls in the body of f/web are in script position, and repeat this procedure as needed to move all other calls into script position.


Exercises

Try converting the following expressions using the above method:


Questions


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