The other day I was reading this blog post by Philip Wadler who was discussing the topic of whether or not continuation-based servers are here to stay. There seems to be a sense that they may be important now, but in the future, their benefits will be lost (thanks to technologies like Ajax).
I'm in the camp that suggests that continuation-based servers will and should be around in the future. And rather than just explaining, I'm going to try show you why.
Here's the problem I wanted to tackle last night. In general, when developing a web application, it's preferred to use plain text and CSS to control the look of the text. However, this greatly limits your font choices, as you can't depend on people's browsers having that esoteric font you want to use. The solution to this problem is to use images instead of plain text to represent parts of the page that need special formatting. This isn't that hard to do on a small scale. But on a larger scale correcting typos, switching fonts, changing wording, etc. can become a real hassle.
To get the best of both words, I decided I would create a framework that generated an image of text on the fly. That way, the web app wouldn't be any harder to maintain than plain text, yet would produce nice looking images.
What does this have to do with continuations? Nothing, yet. I want to demonstrate that solving this problem with continutions is more straightforward than solving it in a traditional web server.
My continuation based server of choices is SISCweb, which I like for all sorts of reasons. Though I think the solution to this problem could actually happen easily in any continuation-based server.
Before we get into the solution itself, just a quick reminder about how SISCweb represents HTML pages -- it takes advantage of SXML. Essentially, it trades < and > for parens. Compare the following example:
<h1>The Mystery</h1>
<p>
It was a <i>dark</i> stormy night ...
<p>
versus
(h1 "The Mystery")
(p
"It was a " (i "dark") " and stormy night...")
Attributes on tags are handled using the notation (@ ...), so you have:
<img src='myimg.gif' border='0' />
versus
(img (@ (src "myimg.gif")
(border 0)))
(If you are thinking yuck! after looking at the above, then it's probably time to read this article.)
And here's my solution to the problem above:
(define (image-text text properties)
`(img (@ (src ,(forward/store! (render text properties)))
(alt ,text)
(border 0))))
(define (render text properties)
(lambda (req)
(let ((image (make-image text properties)))
(send-image/back 'png image))))
(define (make-image text properties)
;; lots of java code to do things like:
;; (1) Creating a buffered image
;; (2) Grab the graphics context
;; (3) Write the string into the graphics context
;; (4) Crop the image
)
The top level function in this code is image-text, which you call like:
(image-text "The Mystery" '((font-family . "Copperplate Gothic Bold")
(font-size . 22)
(padding-left . 10)
(color . "#0033FF")))
image-text works by creating an HTML img tag as one would expect. However, the src of the image tag has a rather strange value, it's:
(forward/store! (render text properties)))
forward/store! is where the magic of continuations come in. forward/store! is provided with a function to call. It doesn't call this function, but instead, returns you back a URL, that when invoked, will call it.
In other words, when the browser goes to retrieve the image (by following the URL of the src attribute), the server will automagically invoke the function returned by render.
If you look at render, it's a function that simply returns another function, which when invoked, will actually call make-image, to generate the needed image.
One of the really cool features of SISCweb is that you can easily call Java from Scheme. In this case, I am taking advantage of the JDK facilities for doing on the fly image generation and encoding.
If you compare the solution above with that of a traditional server, you'll find one key difference. In a traditional server, you need to think of the image tag creation and the actual image generation as two seperate steps. You need to make sure you properly interface the two, by using either the session or query arguments.
With the continuation-based approach, you can think of the process in a natural way. In general the continuation-based server allows you to take any part of your code and generate a URL which, when invoked, will call that code. In the above case, I needed a src attribute that called image generation. In other cases, it may be a src attribute of a JavaScript tag that wants to pull in some AJAX-based code.
One thought that probably comes to mind is: "So what's the big deal? The server generated a URL and a dispatching scheme that I could have hand coded myself without any problems."
True, but one of the lessons we've learned over time in the programming languages world is that the more you can take the burden off the programmer and have him just focus on his problem, the better.
The classic example is garbage collection. Sure, we could all manage our memory manually. It's not that hard on a small scale. But on a big scale, it's a pain. And in the end, you'd just rather not worry about it.
I think continuation servers buy you excatly this - suddenly, tasks which would have required extra thought and code can now be expressed in a natrual way.
The complete solution to the problem I described above also included one other small element. I wrapped the individual image styles in seperate functions, and ended up with:
(define (h1 text)
(image-text text '((font-family . "Copperplate Gothic Bold")
(font-size . 24))))
(define (h2 text)
(image-text text '((font-family . "Century Gothic")
(font-size . 14)
(font-weight . "bold"))))
This allowed me to write HTML as:
`(div ,(h1 "The Mystery")
(p "It was a dark and stormy night ..."))
(For those not used to reading the above notation, the "," above calls h1 as a function, instead of generating the literal text h1>.)
As you can see above, the generated image text looks essentially the same as regular text. Mission accomplished.