« CMSWatch Twenty to Watch | Main | HB211 Is Out of the House »

Building Interactive Web Programs with Continuations

Supposed I asked you to build a program to grab the current exchange rates from the Federal Reserve Bank in New York (FRBNY) in XML, prompt the user for the currency to exchange dollars for, then prompt the user for the dollars to convert, and then display the result. You might write a program that looks like this:

  1. get the rates from the Federal Reserve Bank
  2. show the first prompt and record the currency choice
  3. show the second prompt and record the dollar amount
  4. query the XML to extract the right information using the user input
  5. calculate the result and display it

In fact, other than grabbing and querying the XML, any beginning programming student could write this program. You could probably do it in under 5 minutes in your favorite language.

Now, suppose I asked you to convert your program to a Web application. Still a five minute job? Hardy, and the increase in time is more than just the problem of building the Web pages. What you’d probably do is put each of those steps into a separate script and then bind them all together with a little state machine in the CGI program that jumped to the right script based on state variables that you encoded in the URL, a hidden field, or a cookie. Sadly, it wouldn’t end up looking anything like the program that you’d write if you were just grabbing the input from a terminal.

The problem with programming Web applications is two-fold:

  1. The data from each page has to be managed independently and stored in a persistent manner that lives beyond execution of the CGI script.
  2. The state of the program has to be managed explicitly by the programmer.

When you think about it, Web programming is a lot like assembly language programming—lots of gotos and ad hoc mechanisms for managing data. Dave Weinberger wrote a book called “Small Pieces Loosely Joined” to describe the Web. The problem is that that’s just what Web-based programs look like as well. Dijkstra must be spinning in his grave.

This leads to the question: what if I could write programs for the Web that were “structured” in the programming sense of that word? The result would be Web programs that were more natural to write and easy to read. You’d no longer have to maintain the state of your program outside the language and the data could be kept in variables, where it belongs. The answer is: you can.

There’s a concept in programming languages called a continuation. A continuation is a function that represents the entire forward evaluation of the program from some point in the program. Here’s a simple example. Suppose I’m evaluating this expression:

(+ 4 (* 5 6))

Before I can determine the final sum, I have to compute the product of 5 and 6. So, after the value of (* 5 6) has been computed, we can ask “what remains to be done?” The remaining work can be represented by the following function:

(lambda (x) (+ 4 x))

This is the continuation of the computation from the point immediately after the product is calculated. Some languages make such continuations first-class values and consequently you can get your hands on them and store them in variables so you can do things with them later.

If the language used to program the Web application, and the server, support continuations, the program doesn’t have to terminate to interact with the user. Rather, it can suspend its operation at the point where user interaction is needed and capture the continuation. That continuation can then be used to resume the operation of the program when the user submits the page.

To see how this looks in a real example, let’s return to the programming problem I posed at the beginning of this article. I’ve coded it up as an interactive Web program in Scheme, because Scheme supports continuations. Take a minute and give it a try. Here’s the main body of the program:

(let* (
    ;; get the rates from the federal reserve bank
    (rate-list (get-xml-fr-url (make-rate-url)))
    ;; Show the first page and record the currency choice
    (country-id (extract-binding/single 
             'country-id (get-rate-page rate-list)))
    ;; show the second page and record the prompt amount
    (amount (extract-binding/single 
             'amount (get-dollars-page rate-list country-id)))
    ;; query the XML to get the rate for country-id
    (country-rate (get-rate-for-id country-id rate-list)) 
    ;; extract the values from that rate
    (country (get-country-fr-rate country-rate))
    (currency (get-currency-fr-rate country-rate))
    (date (get-date-fr-rate country-rate))
    (value (get-value-fr-rate country-rate))
    )
 (display-final-page country currency amount value date)
)

If you’re not a Schemer, let me translate. The lines after the let* are executed in sequence and each clause that follows is assigning the value returned by the expression on the right to the variable on the left. So,

(rate-list (get-xml-fr-url (make-rate-url)))

puts the XML that it gets from the FRBNY into a variable called rate-list. Lines beginning with a semicolon are comments. You’ll see that there’s a line in the let* that correspond to each of the steps in the algorithm we gave above. The expression (extract-binding/single ‘foo …) returns the value of the input field named foo on the page. The final result displays the page with the results, which it calculates using the arguments calculated in the lines above.

Of course, there’s more to this program than the main body. (You can see them all by downloading the source.) There are subroutines that support it, but the control flow is exactly the same as what you’d write in any other complete program to do this task outside the Web. Ditto for the data. You just store it in variables and then use those variables when you need them, even after other HTTP interactions with the user. Notice, for example, that we get country-id as the result of the the user submitting the first page, but don’t use it until after the user has submitted the second page. There were no heroics to store it in hidden fields of a database somewhere. We just treated it like any other variable.

My point in this is not to convince you to use Scheme, although I’d applaud you if you did. This trick can be done rather easily in any language that supports continuations (like Python or Smalltalk) and, less easily, in most other languages. Here are some resources that you can use to explore this concept further:

A few notes about the program itself:

  • The program grabs the FRBNY XML file every time its run, but remember, that’s only once per user, not for each page.
  • XPath is used to query the resulting XML.
  • The currency page (the one with the radio buttons) is generated from the XML, so if the FRBNY adds a new currency tomorrow, the application will adjust.
  • In a real application, you’d probably choose the currency and enter the amount on one page. The reason for splitting them here is simply for demonstration purposes.

Posted by windley on February 16, 2005 3:06 PM

See related posts:

1 Comments

www.seaside.st links to is the most recent resources and tutorials on Smaltalk Seaside.

Regards,

Ian.