Ooh, this brings back memories. Continuation-based web frameworks were my original gateway drug to "exotic" programming techniques. Dabble DB was the poster child of Seaside, but didn't get much traction and was acqui-hired by Twitter, whereupon Avi Bryant moved on to other pastures. The other high profile examples of continuation-based web development were Paul Graham's Viaweb (which was acquired by Yahoo and rewritten with more traditional techniques) and Hacker News (which had usability problems due to continuations and mostly phased them out). The verdict of history is clear: continuation-based web development is a sexy but ultimately misguided idea.
No, it wasn't misguided. It worked great, and made writing web applications so much better and easier than it had been.
But Javascript has come a looong way in the last 15 years, and sophisticated web applications do a lot more client-side processing these days. That leads to different architectures, where continuations aren't as advantageous as they had been.
Hacker News is notable in that it doesn't use much client-side code and sticks to an old-school links-and-forms UI. Closures still make a lot of sense for that architecture.
Great comment, but I don't think I agree with your conclusion. HN still has closure-style handlers [1] all through the code—60 or so as of the latest commit. We phased out the ones that were being invoked millions of times, but I'd emphasize that our reason for doing this was implementation-driven, i.e. accidental rather than essential. (This, by the way, is true of many alleged "verdicts of history" in programming. Those who care about good ideas should treat such arguments as guilty until proven innocent, lest we all get sucked completely into popular practice.)
Writing the code to phase out those handlers was a deep lesson for me, because the new code came out more verbose and harder to follow. One rarely gets to do an apples-to-apples comparison of programming techniques on a production system, and somewhat to my surprise the experience convinced me (I was agnostic to begin with) that the closure approach is superior.
There are at least four reasons: (1) the closure has immediate access to all the state that existed when the request was previously processed; (2) the closure is nested inside the code that creates it, so everything you need to understand a full request cycle is in one place; (3) much boilerplate is eliminated, making the code easier to read and write—it can be explicitly at the problem level; (4) security is less of a worry when the client only has to pass back a token instead of all the original parameters to the request, the latter being safely packed away in the closure. That last advantage, i.e. reduced attack surface, was particularly surprising.
Learning this the hard way, by dismantling those advantages and replacing them with manual code in selected cases, taught me something else about this design: it's more than just a clever device. It's really a triumph of two of the greatest programming ideas: first-class functions—more specifically, what used to be called upward funargs [2] back when people were arguing about whether they were a good idea—and lexical scoping. The combination of those two abstractions is what allows this model to shuffle off most of the gruntwork instead of making you, in pg's memorable phrase, a human compiler.
The fact that the design eventually hit scalability problems at millions of users is also a vindication of it, in the sense that it enabled pg to write HN more quickly and safely in the first place. We could selectively deal with the scalability problems later, and have done so. That has worked fine as an engineering tradeoff, but IMO a better solution will come from new implementation ideas that allow another couple orders of magnitude of closures to be kept without slowing down the rest of the system. If we get there, I will take pleasure in rolling all that manual code back into the original design.
1. They're closures, not continuations, which for HN is fine.
2. Meaning the ability to return a function from a function, and thus keep it around indefinitely, as opposed to passing a function to another function as a parameter (downward funargs).
Client-side AJAX apps also allow you to keep state from one request to the next, instead of tossing it back and forth over the net. You can use closures on buttons and everything. In some ways it's even more natural than keeping state on the server, because the client knows exactly how long the page stays open. And it doesn't break when you have lots of users, because they keep the state instead of you. I guess that's part of the reason why AJAX got so popular :-)
That said, if I were building a site like HN, I wouldn't use any of that stuff. I'd go with a bog standard request-response model. There's just not that much state to be preserved. Except maybe draft comments, but these should go in browser storage anyway.
No, I'm not sure. But from a quick look, it seems like most GET handlers on HN take at most two parameters, except for "flag" which takes three. The POST handlers take more, but that's stuff that needs to be sent to the user anyway, because the user is editing it.
Out of the stuff that I did see, the "goto" parameter is the most obvious candidate for the closure treatment. It's also the most obvious candidate for the AJAX treatment, if you add in-place editing like on Reddit, which would be a better UI in my opinion.
Sorry if that sounds arrogant! Of course there could be a lot of functionality that I've missed.
Developing webapps in Seaside is a lot of fun. The ability that Smalltalk gives to you in terms of having a live coding environment, coupled with being able to debug and modify Seaside code in the browser while in development mode, is really powerful. When I first started learning how to program, learning Pharo and Seaside was of tremendous help in understanding how things work. After having used components to build webapps in Seaside, I was simultaneously amused and saddened when I saw it hailed as the next big thing years after Seaside had already done it (disclaimer: I've probably now opened myself up to someone telling me that it was done before Seaside did it!)
The free "Dynamic Web Development with Seaside" book guides you through creating an ajax webapp, but the last time I checked the chapters on persistence were kind of weak. That being said, for toy webapps it's sufficient to just save the state of the Seaside image, with a singleton as your "database".
Indeed, the big plus is really that when an error happens, you can save the state and open a debugger right where the problem happened resume execution. This was a big help when I'd get home at night, I would check if there were errors during the day and would fix them right in the debugger.
Yes, it's explicitly said in the docs that it's based/inspired by Seaside[1]. But note that - last time I checked - Nagare requires Stackless Python and not the standard interpreter. That's because Stackless lets you serialize tasklets and stack frames, which is impossible in normal Python without a custom C extension. It also lacked many useful features, like any kind of authentication handling (but, being WSGI compliant, you probably can plug some middleware to handle this).
This approach to webdev (continuations) could be so cool. I just wish there was a framework on the level of django/rails so that I could feel comfortable using it for a large product.
The Lift web framework in Scala is somehow similar in that aspect. It has been used on multiple bigger sites like foursquare. I really like the approach compared to MVC.
Racket does stateful and stateless servers, and the continuations are either stored on the server or the client per which type you choose to implement [1].
Not just way back; HN uses continuations. (Not in many user-visible places any more, but every link on the front page used to be one—expired "next page" continuation links were a frequent source of grumbling.)
Note that you don't have to expire continuations, necessarily—you could actually persist them statelessly (i.e. without server-side state), by shipping their encoded representations to the client embedded into HMAC-signed links. You're basically giving the server a raw bytecode-eval endpoint, and then making sure that it only accepts code you yourself wrote. Kind of a crazy strategy compared to the standard predeclared REST API, but interestingly flexible.
Now that you mention it, I can't think of very many languages that define a language-standard persistent encoding of a continuation on a closure. One that I can think of is Erlang.
Said spec is nominally "about" the wire protocol Erlang nodes use to communicate with one another in a distributed system. But the ETF format defined there is much more general, and is used all over the place (e.g. when persisting values in Erlang's DETS tables to disk.) It's what you get when you call Erlang's global term_to_binary/1 function.
Erlang's ETF could probably be most closely compared to Python's "pickle" format. (Though, sadly, you can't pickle a lexical closure in Python.)
Even Erlang's approach is sort of a hack. As can be seen from the format spec, Erlang doesn't actually encode the AST of the closure; instead, they ship a reference to a symbol-table ref in a module. Erlang's closures are all implicitly hoisted out into their own gensym'ed functions and then exported from their module to allow for this. Each node assumes the other nodes in the same distributed system all have access to the same modules (not that they all necessarily have it loaded, but that they can load it Just-In-Time when dereferencing the closure) and so the export-table reference can be turned back into a handle on the closure at the receiving end by looking it up in its module.
What this all means is that, although Erlang nodes can rehydrate closures from other nodes that are running the same code, they can't rehydrate arbitrary closures defined and serialized at your personal REPL running different code. (Which defeats half the purpose; you can't then use dehydrated closures as an arbitrary query/callback/RPC syntax between Erlang-based microservices, unless they all share code.)
On the other hand, there's nothing explicitly stopping the Erlang Run Time System from supporting the full version, where a closure is dehydrated as its AST (plus free vars, as in ETF encoding.) And so this is exactly what Elixir does. Yet another reason Elixir is an interesting language.
So what's encoded are the closed variables along with the gensym'ed name of the closure (effectively a pointer to the code)? If so, that's pretty useful. For some cases it's better than serializing the code, because then you don't have to pass a copy of the code around everywhere, only the state that you don't keep a copy of.
It uses continuations to hide the asynchronous nature of web requests and allows the flow between pages of a web app to be written in an imperative style.
There are a couple of continuation-based web frameworks for Ruby (Borges, Wee), but they're old and abandoned as far as I know.
I suspect it didn't catch on because (1) people are afraid of continuations, and (2) web apps are typically designed in at least a pseudo-REST-like style, so the paradigm of "asking" the user (like "await" in ES6) isn't needed very often.
But it's a cool thing to be able to do, especially for funnel-type flows, and other types of more complex session-based interactions.
I wonder if there are any JavaScript inversion of control things for client side code. I've been working for the past week on a signup funnel with React and Flux, and it's just a big explicit state machine... which kind of works, but is also kind of tedious.
There were client side JavaScript continuation like things. Back in the mid-2000's there was Narrative JavaScript. I used it to implement threads and Alice ML style futures/promises on the client side: https://bluishcoder.co.nz/2006/06/05/more-concurrency-in-nar...
Can I ask: assuming that Smalltalk and Seaside are the bee's knees, who are your customers? Who do you sell Seaside solutions to?
I'm not a Smalltalk developer; I ask this as someone who finds himself missing Objective-C's dynamism when programming C# or Java, so I'm sympathetic to the advantages of Smalltalk. But customers know .NET and Java. That's not a hard sell.
Personally I still have a seaside application, I sell the service so clients get a user account on the web application that I host myself.
I wouldn't attempt selling the application as something a client would continue developing on.
Well, it's as responsive as design it to be. The framework only gives you the building blocks(tags), you can still use bootstrap or your own responsive design with it
Smalltalk is the most lively environment you can have. Pure OO with direct feedback. Not the usual dead code in files with long cycles as in more mainstream technologies
It is true that meanwhile single page applications have hit the client web space - but you can address them easily with the Seaside-REST package (for instance sending JSON to clients side AngularJS). If you need a framework with less footprint check out "Teapot" which you can easily run on Pharo (http://www.pharo.org).