
Web Programming with Continuations (2002) [pdf] - dmux
https://thelackthereof.org/docs/library/cs/continuations.pdf
======
slaymaker1907
One of the Racket tutorials actually goes into an example of using
continuations to save application state for a web app ([https://docs.racket-
lang.org/more/index.html#%28part._.Conti...](https://docs.racket-
lang.org/more/index.html#%28part._.Continuations%29)).

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.

~~~
gwright
I don't think you need to pass closures between machines but you do need to
use "sticky sessions" so that your load balancers deliver all requests for a
particular session back to the same app instance.

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.

~~~
muxator
An application requiring sticky session sounds like an architecture smell to
me. It works, and you can get a long way with it, but a system that relies on
requests always being served by the same server sounds a bad idea to me. What
if that server breaks/need a security update/has to be rebooted? For security
updates & restarts you can always use session draining, but then your
operativity becomes dependent on an upstream service (a LB?), which in a large
organization is probably handled by another team, and you end up needing to
coordinate and complicate something that could have been simple.

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.

~~~
soegaard
These are serious concerns. An attempt to make web programming with
continuations work without requiring sticky sessions is "Stateless Servlets":

[https://docs.racket-lang.org/web-
server/stateless.html?q=sta...](https://docs.racket-lang.org/web-
server/stateless.html?q=stateless#%28part._considerations%29)

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.

------
elviejo79
A framework that works with continuations is Seaside in smalltalk, it is a
delight to work with it. [https://seaside.st](https://seaside.st)

~~~
chrismorgan
That HTTPS link is broken for me, PR_CONNECT_RESET_ERROR in Firefox.
[http://seaside.st](http://seaside.st) works.

------
anuragsoni
As mentioned in the document, continuation passing style can at times be a
little difficult to read. That being said I found a talk that using
continuation passing style to provide typed routing. [1]

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...](https://dbp.io/talks/2016/fn-continuations-haskell-meetup.pdf)

[2]
[https://github.com/anuragsoni/routes](https://github.com/anuragsoni/routes)

[3]
[https://github.com/anuragsoni/routes/blob/0.1.0/src/routes.m...](https://github.com/anuragsoni/routes/blob/0.1.0/src/routes.ml)

~~~
zeveb
> As mentioned in the document, continuation passing style can at times be a
> little difficult to read.

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.

------
gawi
Around the same period, in the telephony world, IVR (interactive voice
response) systems entered the web universe with the VoiceXML W3C
recommendation. It allowed IVR developers to write server-side web
applications as ordinary web applications, the HTML being substituted by
VoiceXML.

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.

------
saurik
Also from 2002 I believe, an example of a project that was doing this with
JavaScript using Rhino, the at-the-time state of the art Java implementation
of the language that supported capturing a continuation.

[https://cocoon.apache.org/2.1/userdocs/flow/continuations.ht...](https://cocoon.apache.org/2.1/userdocs/flow/continuations.html)

------
draegtun
It should be noted that the owner/author of this website
([https://thelackthereof.org/](https://thelackthereof.org/) and not the posted
PDF) wrote a Perl _continuations_ based web library/framework called
Continuity.

\-
[https://thelackthereof.org/Continuity](https://thelackthereof.org/Continuity)

\- [https://metacpan.org/pod/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](https://news.ycombinator.com/threads?id=awwaiid))
for the great work creating Continuity.

------
brlewis
Many of the comments here seem worried that scalability issues are going to
crop up everywhere as people start using this architecture in large, popular
applications. That isn't going to happen. Stop worrying about that and enjoy
it for the cool hack that it is.

------
kristianp
Paul Grahams mentions Closures and continuation passing style in Viaweb here:
[http://ep.yimg.com/ty/cdn/paulgraham/bbnexcerpts.txt](http://ep.yimg.com/ty/cdn/paulgraham/bbnexcerpts.txt)

------
sn41
I am new to this, but isn't the idea of calling next() which is prevalent in
frameworks like express.js, essentially similar to continuations? I may be
wrong, but I would genuinely like to know.

~~~
tlb
Yes. It’s easy when you can run it all client-side. The tricky part described
in the article is making it work across no-JS browser and webserver.

------
erikpukinskis
Isn’t this how HN is implemented?

~~~
EvanAnderson
I know that it was in the past, but I believe it changed a few years ago.

See:
[https://news.ycombinator.com/item?id=7651568](https://news.ycombinator.com/item?id=7651568)

------
colanderman
Please don't design web applications like this. It's counter to the core ideas
of HTTP and REST. It's just about as bad a design as is ASP shoveling all its
state into POST requests.

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.

~~~
amelius
> 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).

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.

~~~
colanderman
> You could apply encryption

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...](https://en.wikipedia.org/wiki/Representational_state_transfer#Statelessness)

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...](https://en.wikipedia.org/wiki/Representational_state_transfer#Relationship_between_URI_and_HTTP_methods)

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...](https://en.wikipedia.org/wiki/Representational_state_transfer#Cacheability)

At least this technique _is_ pure HATEOAS, so there's that:
[https://en.wikipedia.org/wiki/HATEOAS](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.

~~~
0x445442
> But state is integral to the experience of a web application

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.

~~~
colanderman
Right, which is why session state should be kept on the client, and why the
client should manipulate application state (i.e., everything that lives in a
database) using a well-defined RESTful interface.

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.

~~~
amelius
> The approach in the OP seems to conflate these two types of state.

Nope, I'm just saying that continuations and REST are orthogonal.

