[Update: see further contributions to the discussion from Ian Griffiths, Avi Bryant, James Robertson, and Joe Duffy; note also John Cowan’s excellent comment below, pointing out that hidden fields work with the back button but not with bookmarks.]
It looks like continuations are back on the discussion board (Gilad Bracha, Tim Bray, and Don Box). I spent some time with Scheme a decade ago and continuations were one of the new features I had to try to understand. Then, as now, I found them more clever than practical.
Gilad sets up a use case for continuations before he goes on to oppose them: in essence, a web application could use continuations to maintain separate stacks, so that as a user hits the back button and then starts down new paths, the web application would not become confused, selling the user a trip to Hawaii instead of Alaska. I can see how continuations would work for that, just as I can see how a bulldozer could turn over the sod in my garden, but I’m far from convinced that either is the right tool for what is really a much simpler problem.
Explicit state
First, a continuation preserves the entire state of a program, including the stack, instruction counter, local variables, etc. How much of that do you really need for a hypothetical travel web app? In reality, you probably need, maybe, 1-5 variable values to restore a previous state in the travel app, so why not just save those explicitly? It would be faster, more secure (less information being saved), and much easier to performance tune and debug (since no magic is happening behind the scenes). Save those variables in a database, in a hash table, in an XML or CSV file, in memcached, or wherever happens to be most convenient. You may be looking at under 100 bytes for each saved state, so if you really want to do this, it’s not going to hurt too badly.
REST
But do you really want to do this? Most of the discussion around REST has focussed on the use of persistent URLs and how to use HTTP verbs like GET, POST, PUT, and DELETE, but there’s another, perhaps more critical idea behind REST — that the resource your retrieve (a web page, XML document, or what-have-you) contains its own transition information.
Let’s say that you load a web page into your browser, load more web pages, then use the back button to return to the original one. Now, select a link. What happens? Did you browser have to go back to the original web server, which was using continuations (or other kinds of saved state) to keep track of the links from every page you visited, so that it won’t send you to the wrong one? Of course not. The web page that you originally downloaded already included a list of all its transitions (links), and intuitive things just happen naturally when you hit the back button.
The web is stateless, but web application toolkits maintain pseudo-sessions (using cookies, URL rewriting, or what-have-you) that makes them look stateful, and that makes programmers lazy. Obviously, you don’t want to stick information like ‘isauthenticated’ on a web page, since it could be forged; likewise, you don’t want to put a credit-card number there. But it is trivially simple to make sure that forms, like links, go to the right place even when you hit the back button — just make the transitions fully independent of any session stored on the server side. For example, consider this:
<form method="post" action="/actions/book-trip"> <button>Book this trip!</button> </form>
Presumably, the trip the person was looking at is stored somewhere in a session variable on the browser. DON’T DO THIS! As Gilad pointed out, someone hitting the back button might end up booking the wrong trip. There are gazillions of ways to push all of the context-sensitive stuff into the web page itself, where it belongs. Here’s one example:
<form method="post" action="/actions/book-trip"> <label>Book your economy trip to Alaska!</label> <input type="hidden" name="destination" value="alaska"/> <input type="hidden" name="package" value="economy"/> <button>Book it.</button> </form>
Here’s another:
<form method="post" action="/actions/book-trip/alaska/economy"> <label>Book your economy trip to Alaska!</label> <button>Book it.</button> </form>
This is 100% backbutton-proof and it’s trivially simple to implement. It took me a while after reading Gilad’s (admittedly, strawman) example to realize that there are people who do not develop webapps this way. If they do this much damage just with a Session stack, how much pain will they be able to cause with continuations?
The REST people are right, at least on this point: there’s no need to drive a continuation bulldozer through your webapp, when a little REST garden spade will work quite nicely (and won’t tear up your lawn in the process). Don suggests that there may be other, more legitimate use cases for continuations outside of web applications, and I have no reason to disagree, but I would like to look at them pretty carefully.
and here I’ve been feeling that using hidden state variables is so amazingly irritating; it makes me feel like I’m using a dirty hack to achieve stateful information flow.
I do see what you’re getting at, though; make the webapp pages be entirely atomic, to prevent problems with needing to maintain state in the back end.
Thanks for the comment, Aurynn. Here’s one way to think of it that might make you feel less dirty — you set up your action to receive a certain set of input parameters, and you set up the form so that the browser will submit that set of input parameters. Whether the parameters come from a user typing in a field or from static information stored inside the HTML document should be entirely an interface detail (in real life it’s not, unfortunately, because for error reporting your action has to know what fields can be corrected in a resubmission by the user, and which signal an internal error of some kind).
Personally, I prefer to use the URL over hidden fields when it makes sense. You can even post to a URL that has get parameters in a query string, but that’s probably getting too strange.
Ian Griffiths has more to say on that topic here:
http://www.interact-sw.co.uk/iangblog/2006/05/21/webcontinuations
Pingback: HREF Considered Harmful » Blog Archive » Ongoing Continuations
You don’t need to store all local variables, etc. With a newer innovation called delimited continuations, you can limit the extent of a continuation to a particular block of code. Things become both more clear and possibly more efficient. See
http://community.schemewiki.org/?composable-continuations-tutorial
And there’s no rule that says you can’t serialize continuations and put them in a hidden field on a client instead of on the server.
The problem with continuations on the Web is where to store them. Since we don’t have (and never will have) a distributed garbage collector for the Web, we don’t know when it’s safe to discard the continuation.
There are four possibilities:
1) Store the continuation in the URL. This is perfect if you can make it small enough to fit within the constraints of usable URLs.
2) Store the continuation on the server and use the URL as a key to find it. The difficulty here is that you have to throw these away eventually or the server runs out of space. When is it safe to discard the continuation? You have to guess. (Naturally, you mustn’t ever reuse such a key, but that’s fairly easy to arrange.)
3) Store the continuation on the client in a cookie. Same issue as #1, but less limited.
4) Store the continuation in hidden fields. The trouble with this is that in order to save, the user has to save the whole page, not just the URL. Most users aren’t used to that idea.
My best idea so far is a combination of #2 and #4. Store the whole page, complete with hidden fields, on the server and return an URL to it. People who bookmark the page will fetch it from the server and can carry on from there. Eventually the server will have to discard the page, after which bookmarking won’t work, but those who have been forethoughtful enough to save the page locally will still be able to carry on safely. Do your best to warn people of the necessity of
saving if they are going to leave the page for, say, a day (or whatever the server’s garbage-collection threshold is).
Joe Duffy has a post with greater technical depth here:
http://www.bluebytesoftware.com/blog/PermaLink,guid,db077b7d-47ed-4f2a-8300-44203f514638.aspx
Dilip: thanks for both references.
Now this is the kind of article I can stand behind đ Well stated David!
One additional piece of “workflow management”. It can be a bit controversial because it changes the state of the back button to something the user is not expecting. However, when there are situations where you are half way through a transaction, of which hitting the back button would have negative impact, sometimes there’s just no other way.
What I’m refering to is document.replace, which will replace not only the current document, but will replace the history stack with the same URI, so hitting the back button, in essence, takes you to the same place you are already at, and hitting it twice takes you to the beginning of the transaction (if used correctly.)
“replace the history stack” should be “replace the first item in the history stack” using a FIFO style method.
Pingback: links for 2006-06-01 at protocol7