Continuations in Clojure

11,255

Solution 1

When talking about continuations, you’ll have to distinguish between two different kinds of them:

  • First-class continuations – Continuation-support that is deeply integrated in the language (Scheme or Ruby). Clojure does not support first-class continuations.

  • Continuation-passing-style (CPS) – CPS is just a style of coding and any language supporting anonymous functions will allow this style (which applies to Clojure too).

Examples:

-- Standard function
double :: Int -> Int
double x = 2 * x

-- CPS-function – We pass the continuation explicitly
doubleCPS :: Int -> (Int -> res) -> res
doubleCPS x cont = cont (2 * x)
; Call
print (double 2)

; Call CPS: Continue execution with specified anonymous function
double 2 (\res -> print res)

Read continuation on Wikipedia.

I don’t think that continuations are necessary for a good language, but especially first-class continuations and CPS in functional languages like Haskell can be quite useful (intelligent backtracking example).

Solution 2

I've written a Clojure port of cl-cont which adds continuations to Common Lisp.

https://github.com/swannodette/delimc

Solution 3

Abstract Continuations

Continuations are an abstract notion that are used to describe control flow semantics. In this sense, they both exist and don't exist (remember, they're abstract) in any language that offers control operators (as any Turing complete language must), in the same way that numbers both exist (as abstract entities) and don't exist (as tangible entities).

Continuations describe control effects such as function call/return, exception handling, and even gotos. A well founded language will, among other things, be designed with abstractions that are built on continuations (e.g., exceptions). (That is to say, a well-founded language will consist of control operators that were designed with continuations in mind. It is, of course, perfectly reasonable for a language to expose continuations as the only control abstraction, allowing users to build their own abstractions on top.)

First Class Continuations

If the notion of a continuation is reified as a first-class object in a language, then we have a tool upon which all kinds of control effects can be built. For example, if a language has first-class continuations, but not exceptions, we can construct exceptions on top of continuations.

Problems with First-Class Continuations

While first-class continuations are a powerful and useful tool in many cases, there are also some drawbacks to exposing them in a language:

  • Different abstractions built on top of continuations may result in unexpected / unintuitive behavior when composed. For example, a finally block might be skipped if I use a continuation to abort a computation.
  • If the current continuation may be requested at any time, then the language run-time must be structured so that it is possible to produce some data-structure representation of the current continuation at any time. This places some degree of burden on the run-time for a feature which, for better or worse, is often considered "exotic". If the language is hosted (such as Clojure is hosted on the JVM), then that representation must be able to fit within the framework provided by the hosting platform. There may also be other features a language would like to maintain (e.g., C interop) which restrict the solution space. Issues such as these increase the potential of an "impedence mismatch", and can severely complicate development of a performant solution.

Adding First-Class Continuations to a Language

Through metaprogramming, it is possible to add support for first-class continuations to a language. Generally, this approach involves transforming code to continuation-passing style (CPS), in which the current continuation is passed around as an explicit argument to each function.

For example, David Nolen's delimc library implements delimited continuations of portions of a Clojure program through a series of macro transforms. In a similar vein, I have authored pulley.cps, which is a macro compiler that transforms code into CPS, along with a run-time library to support more core Clojure features (such as exception handling) as well as interop with native Clojure code.

One issue with this approach is how you handle the boundary between native (Clojure) code and transformed (CPS) code. Specifically, since you can't capture the continuation of native code, you need to either disallow (or somehow restrict) interop with the base language or place a burden on the user of ensuring the context will allow any continuation they wish to capture to actually be captured.

pulley.cps tends towards the latter, although some attempts have been made to allow the user to manage this. For instance, it is possible to disallow CPS code to call into native code. In addition, a mechanism is provided to supply CPS versions of existing native functions.

In a language with a sufficiently strong type system (such as Haskell), it is possible to use the type system to encapsulate computations which might use control operations (i.e., continuations) from functionally pure code.

Summary

We now have the information necessary to directly answer your three questions:

  1. Clojure does not support first-class continuations due to practical considerations.
  2. All languages are built on continuations in the theoretical sense, but few languages expose continuations as first-class objects. However, it is possible to add continuations to any language via, e.g., transformation into CPS.
  3. Check out the documentation for delimc and/or pulley.cps.

Solution 4

Is continuation a necessary feature in a language?

No. Plenty of languages don't have continuations.

If no, dont you need continuations? I have seen a lot of good examples especially from this guy. What is the alternative?

A call stack

Solution 5

A common use of continuations is in the implementation of control structures for: returning from a function, breaking from a loop, exception handling etc. Most languages (like Java, C++ etc) provide these features as part of the core language. Some languages don't (e.g: Scheme). Instead, these languages expose continuatiions as first class objects and let the programmer define new control structures. Thus Scheme should be looked upon as a programming language toolkit, not a complete language in itself.

In Clojure, we almost never need to use continuations directly, because almost all the control structures are provided by the language/VM combination. Still, first class continuations can be a powerful tool in the hands of the competent programmer. Especially in Scheme, continuations are better than the equivalent counterparts in other languages (like the setjmp/longjmp pair in C). This article has more details on this.

BTW, it will be interesting to know how Rich Hickey justifies his opinion about continuations. Any links for that?

Share:
11,255
unj2
Author by

unj2

.

Updated on June 01, 2022

Comments

  • unj2
    unj2 almost 2 years

    I read somewhere where rich hickey said:

    "I think continuations might be neat in theory, but not in practice"

    I am not familiar with clojure.
    1. Does clojure have continuations?
    2. If no, don't you need continuations? I have seen a lot of good examples especially from this guy. What is the alternative?
    3. If yes, is there a documentation?