All Your Closures Are Belong To Us
Wednesday, August 3, 2005
I was originally going to call this weblog entry "Developing
Continuation-Based Web Applications with UCW" but that sounded way too
boring!
I really liked the
UCW introduction tutorial movie that Marco
Baringer did the other day. Introductory material is important to draw
people to software. However, he didn't really provide much in the way of
an explanation as to why
Uncommon Web (UCW) was different from other
CL web packages. The key point that he should have
illustrated (and, hopefully, will demonstrate in a future movie
[hint, hint]) is that UCW is a
continuation-based web application framework. Ok, you might ask, so what does
that mean in practice? Well, for one, it means that your development
model stays pretty consistent regardless of whether you are developing
for the web or not. For example, say I wanted to develop a simple
program that iteratively prompts a user for some numbers and prints out the total
of those numbers. If I wrote this as a basic, non-web program, I
might write something like the following:
(defun read-a-number (&optional (question "A number please")) (parse-integer (progn (format t "~a: " question) (read-line))))So, my simple example consists of a single "helper" function to handle user input/output and a chunk of code that performs the logic. (Ok, it's got no error handling and you have to break it to exit, but bear with me for now ;-)). Now, what if I wanted to write that same simple program for the web? Since web apps are inherently "stateless" and each request for a number requires that you send a new page to the user, you need some way to capture "application state" (e.g. - the numbers that the user has entered so far, the maximum number of values that the user wanted to enter and the count of how many numbers have been entered so far). There are a number of ways to do this, but, in practice, it is normally done by using one (or more) of the following techniques:
(loop for how-many = (read-a-number "How many numbers should we read?") do (loop repeat how-many sum (read-a-number) into total finally (format t "The sum is: ~D.~%" total)))
- Cookies
- URL rewriting
- Hidden form variables
- Persistant store
(defcomponent read-a-number (widget-component) ((label :accessor label :initarg :label :initform "A number please")))I've deliberately left out some of the "bits & pieces" of the UCW web app in order to illustrate the similarities in the code. The full code (only slightly longer) is provided as one of Marco's examples. However, the similarities between the non-web app and the web app are readily apparent. By handling session state "behind the covers", continuation-based web application frameworks allow developers to write code for the web in a manner similar to how they write code for non-web applications without having to drastically restructure the logic of their applications. This is covered in detail in the excellent paper "Automatically Restructuring Programs for the Web". If you are interested in this approach to writing web applications, there is additional information about continuation-based web application frameworks and techniques at my links page on the topic.
(defaction ok ((reader read-a-number) &optional number-string) (let ((num (parse-integer number-string :junk-allowed t))) (when num (answer num))))(defaction start ((s sum)) (loop for how-many = (call 'read-a-number :label "How many numbers should we read?") do (loop repeat how-many sum (call 'read-a-number) into total finally (call 'info-message :message (format nil "The sum is: ~D." total)))))

