Web Programming with Continuations (2002) [pdf]
thelackthereof.org
thelackthereof.org
One issue with this approach is that it doesn't really scale easily beyond a single server. You need something like the JVM which allows for closures to be sent between machines sharing the same code base. Plus, even in the JVM case, you must have the class files required by the closure available on the JVM which tries to run it.
I really think it is a better idea to just use a database and pass state explicitly rather than trying to do something fancy with continuations. However, continuations can be extremely useful for making the server asynchronous without making it look asynchronous.
While Promises are nice, I think they are severely flawed in that they must "infect" any code which tries to use them. For instance, I was dealing with some Typescript compiler interfaces a few years back which expected to be able to read files synchronously, something which does not hold true if you try to emulate a filesystem in the browser using web requests. With true continuations, it would be possible to simply pause this computation transparently and then resume it once the request was complete.
I haven't seen much discussion of sticky sessions in quite a while as 12-factor apps built around stateless processes have no need for them and that has been the dominant paradigm for quite a while.
As long as it's a conscious choice, it's ok not baking in tolerance for these failure modes, but personally I would only accept it for POCs/prototypes.
And for what? Just so you can run some logic on the server? Which you really shouldn't be doing in the first place. And who is the lucky person that has to deal with server affinity? Seems like the developer is just tossing his work over the fence to some poor dev ops guy to clean up.
https://docs.racket-lang.org/web-server/stateless.html?q=sta...
A stateless servlets makes it possible to serialize continuations and store them on the client/database/disk.
There is no such thing as a free lunch, so that approach has pros and cons too.
I found for distribute environment, the big idea is still messaging.
https://docs.racket-lang.org/web-server-internal/closure.htm...
I prefer to explicitly serialize state, because its easier to reason about. Its harder to assert that request A was definitely using/not-using some bit of state that request B was using when you are automatically serializing a bunch of lexical variables in the background.
This is just the bias of my personal experience though, I'm a pretty low-level guy.
This isn’t something out of the ordinary, you would think - but I can easily see how that could be hard to implement.
This won't be the most efficient way to perform routing in an application, but i found the implementation really simple to understand and the error messages for the end-user seem really clear as well. It also helps if the language used allows for embedding DSLs that are easy to read for the end-user. (could be macros in lisp/elixir, well named functions with a little sprinkling of infix operators in OCaml, Haskell etc)
A little show HN in the comments: I've been exploring this approach and others [2] for comparison, and although i've moved away from this style as I progress in learning more OCaml, it was a fun little exercise to explore a CPS based typed routing approach. [3]
[1] https://dbp.io/talks/2016/fn-continuations-haskell-meetup.pd...
[2] https://github.com/anuragsoni/routes
[3] https://github.com/anuragsoni/routes/blob/0.1.0/src/routes.m...
I never had to make it scale though, but it is an amazing experience.
Very true. Fortunately, the CPS transform is automatic, so one could write a compiler which compiles non-CPS to CPS code.
This is pretty easy in Scheme or Lisp (less so the latter, because it's a much more complex & powerful language); it's not nearly easy in other languages, but still doable.
Given the very linear nature of an IVR dialog, it became clear that a continuation-based approach would greatly simplify development, as mentioned in the article.
The Nu Echo "Rivr" Java framework was created around this idea. However, since there is no native continuation in Java (yet!), it was approximated through thread-based coroutines.
The payoff for the developers is huge. It allows the developer to directly "map" a dialog specification to Java code. Abstractions can easily be introduced without having to create an explicit model for the state (the state is just the set of the variables and objects referred by the thread). This approach is much more flexible than state-machine based paradigms.
Even though all architectural issues mentioned here are valid (namely the lack of session-based fault-tolerance), it turned out to be somewhat manageable in real-life situations because in this type of system, there is a limited number of calls handled simultaneously and their average duration is typically a few minutes.
Rivr is still used today and we are currently defining mechanisms with HAProxy to implement no-downtime scenarios in Kubernetes.
https://cocoon.apache.org/2.1/userdocs/flow/continuations.ht...
- https://thelackthereof.org/Continuity
- https://metacpan.org/pod/Continuity
Shame it is no longer under development. Found it very handy & enjoyable using it for projects in the past (by itself and sometimes with Squatting framework built on top of it). Hats off to Brock (https://news.ycombinator.com/threads?id=awwaiid) for the great work creating Continuity.
EDIT: Also the fnid issue that EvanAnderson mentioned.
Not to mention that you have to choose between either dealing with garbage-collecting continuations stored on the server (choosing between either breaking client bookmarks, or bloating the server with never-to-be-used junk), or worrying about the security implications of storing continuations on the client (remembering that a continuation is arbitrary code which will executed on your server).
And since you don't have a well-defined HTTP API, but rather one directly coupled to the structure of your source code, all those links will break every time you change your code.
This design was notably promulgated by the Racket (then Dr. Scheme) crowd, who were (and I assume still are) notoriously allergic to state and so used mechanisms like this to try to hide it.
Please don't try to hide state. Especially in truly stateful systems like HTTP and user interfaces. Embrace state. Minimize it, isolate it, document it, control it, model it, but embrace it.
You could apply encryption (with proper randomization to deal with possible replay attacks, if necessary e.g. if you keep state on the server).
> Embrace state. Minimize it, isolate it, document it, control it, model it, but embrace it.
But continuations/closures are a way to control state. It's just that the state is not accessible from the outside of the system (the user).
> It's counter to the core ideas of HTTP and REST.
No, it isn't really. If you are sending continuations along with a request, then the server can still behave in a purely functional way.
Or you could identify which bits of state actually matter and define a stable REST API, instead of identifying application state as "any state the interpreter could possibly be in" in an attempt to pretend that state doesn't exist.
> But continuations/closures are a way to control state.
No, they're a way to hide state. But state is integral to the experience of a web application. Hiding it is just a leaky abstraction (for the reasons pointed out above).
> the server can still behave in a purely functional way
You're going to have to explain how that makes this technique RESTful.
Notably, if you store the continuation on the server, you're violating REST's statelessness requirement: https://en.wikipedia.org/wiki/Representational_state_transfe...
Rolling up all the system's state into a single URI whose underlying state cannot be modified violates the requirements that each URI represent a distinct resource which can be manipulated (i.e. changed) using standard HTTP verbs: https://en.wikipedia.org/wiki/Representational_state_transfe...
Once you have persistent data (say, flight details stored in a database), you lose cacheability, since each interaction with that data from a different source is via a different URI: https://en.wikipedia.org/wiki/Representational_state_transfe...
At least this technique is pure HATEOAS, so there's that: https://en.wikipedia.org/wiki/HATEOAS
I don't disagree that there are places within a web application where this technique makes sense. The one example of this technique which is always presented – a short-lived dialogue-based interaction with the user – is one of them. That particular state is sufficiently short-lived, and doesn't need to be cacheable or shareable with others, that pretending it doesn't exist is probably helpful in the long run.
But I posit that in general, it's always a better architectural decision to explicitly identify your state (i.e., everything which you're keeping in a database or on disk) and minimize it.
I think you misunderstand continuations, as they are closures which often do contain state. In fact, they can be used to capture all of the state relevant to the session.
I'd have no problem with this technique if, instead of an arbitrary call/cc or what-have-you, the developer had to explicitly name the continuation, and specify and name which bits of state are relevant. This definition could then form the basis of a well-defined public-facing interface, for which application tooling could ensure smooth upgrades, for which security properties could be easily demonstrated, and for which more stringent persistence rules than heuristic-based garbage collection could be defined.
[0] https://docs.racket-lang.org/web-server/stateless.html#(part...
HTTP is a stateless protocol and the desire to maintain a state-full session on the server is why web applications are fundamentally broken, at least within the context of HTTP.
The approach in the OP seems to conflate these two types of state. The end result is application state without a well-defined client interface, and session state which is passed backed and forth to the server.
Nope, I'm just saying that continuations and REST are orthogonal.
It's not, as long as the server state is designated by a URL. The client-side continuation is perfectly compatible with REST, and it's safety of client-side continuations is preserved with crypto.