---
title: Delimited Continuations, Urn and Lua
date: August 1, 2017
---

As some of you might know, [Urn](https://squiddev.github.io/urn) is my
current pet project. This means that any potential upcoming blag posts
are going to involve it in some way or another, and that includes this
one. For the uninitiated, Urn is a programming language which compiles
to Lua[^1], in the Lisp tradition, with no clear ascendance: We take
inspiration from several Lisps, most notably Common Lisp and Scheme.

As functional programmers at heart, we claim to be minimalists: Urn is
reduced to 12 core forms before compilation, with some of those being
redundant (e.g. having separate `define` and `define-macro` builtins
instead of indicating that the definition is a macro through
a parameter). On top of these primitives we build all our abstraction,
and one such abstraction is what this post is about: _Delimited
continuations_.

Delimited continuations are a powerful control abstraction first
introduced by Matthias Felleisein[^2], initially meant as
a generalisation of several other control primitives such as
`call-with-current-continuation`{.scheme} from Scheme among others.
However, whereas `call-with-current-continuation`{.scheme} captures
a continuation representing the state of the entire program after that
point, delimited continuations only reify a slice of program state. In
this, they are cheaper to build and invoke, and as such may be used to
implement e.g. lightweight threading primitives.

While this may sound rather limiting, there are very few constructs that
can simultaneously be implemented with
`call-with-current-continuation`{.scheme} without also being expressible
in terms of delimited continuations. The converse, however, is untrue.
While `call/cc`{.scheme} be used to implement any control abstraction,
it can't implement any _two_ control abstractions: the continuations it
reifies are uncomposable[^3].

### Delimited Continuations in Urn

Our implementation of delimited continuations follows the Guile Scheme
tradition of two functions `call-with-prompt` and `abort-to-prompt`,
which are semantically equivalent to the more traditional
`shift`/`reset`. This is, however, merely an implementation detail, as
both schemes are available.

We have decided to base our implementation on Lua's existing coroutine
machinery instead of implementing an ad-hoc solution especially for Urn.
This lets us reuse and integrate with existing Lua code, which is one of
the goals for the language.

`call-with-prompt` is used to introduce a _prompt_ into scope, which
delimits a frame execution and sets up an abort handler with the
specified tag. Later on, calls to `abort-to-prompt` reify the rest of
the program slice's state and jump into the handler set up.

```lisp
(call/p 'a-prompt-tag
  (lambda ()
    ; code to run with the prompt
    )
  (lambda (k)
    ; abort handler
    ))
```

One limitation of the current implementation is that the continuation,
when invoked, will no longer have the prompt in scope. A simple way to
get around this is to store the prompt tag and handler in values and use
`call/p`[^4] again instead of directly calling the continuation.

Unfortunately, being implemented on top of Lua coroutines does bring one
significant disadvantage: The reified continuations are single-use.
After a continuation has reached the end of its control frame, there's
no way to make it go back, and there's no way to copy continuations
either (while we have a wrapper around coroutines, the coroutines
themselves are opaque objects, and there's no equivalent of
`string.dump`{.lua} to, for instance, decompile and recompile them)

### Why?

In my opinion (which, like it or not, is the opinion of the Urn team),
Guile-style delimited continuations provide a much better abstraction
than operating with Lua coroutines directly, which may be error prone
and feels out of place in a functional-first programming language.

As a final motivating example, below is an in-depth explanation of
a tiny cooperative task scheduler.

```lisp
(defun run-tasks (&tasks) ; 1
  (loop [(queue tasks)] ; 2
    [(empty? queue)]    ; 2
    (call/p 'task (car queue)
      (lambda (k)
        (when (alive? k)
          (push-cdr! queue k)))) ; 3
    (recur (cdr queue)))) ; 4
```

1. We begin, of course, by defining our function. As inputs, we take
   a list of tasks to run, which are generally functions, but may be Lua
   coroutines (`threads`) or existing continuations, too. As a sidenote,
   in Urn, variadic arguments have `&` prepended to them, instead of
   having symbols beginning of `&` acting as modifiers in a lambda-list.
   For clarity, that is wholly equivalent to `(defun run-tasks (&rest
   tasks)`{.lisp}.

2. Then, we take the first element of the queue as the current task to
   run, and set up a prompt of execution. The task will run until it
   hits an `abort-to-prompt`, at which point it will be interrupted and
   the handler will be invoked.

3. The handler inspects the reified continuation to see if it is
   suitable for being scheduled again, and if so, pushes it to the end
   of the queue. This means it'll be the first task to execute again
   when the scheduler is done with the current set of working tasks.

4. We loop back to the start with the first element (the task we just
   executed) removed.

Believe it or not, the above is a fully functioning cooperative
scheduler that can execute any number of tasks.[^5]

### Conclusion

I think that the addition of delimited continuations to Urn brings
a much needer change in the direction of the project: Moving away from
ad-hoc abstraction to structured, proven abstraction. Hopefully this is
the first of many to come.


[^1]: Though this might come off as a weird decision to some, there is
a logical reason behind it: Urn was initially meant to be used in the
[ComputerCraft](https://computercraft.info) mod for Minecraft, which
uses the Lua programming language, though the language has outgrown it
by now. For example, the experimental `readline` support is being
implemented with the LuaJIT foreign function interface.

[^2]: [The Theory and Practice of First-Class
Prompts](http://www.cs.tufts.edu/~nr/cs257/archive/matthias-felleisen/prompts.pdf).

[^3]: Oleg Kiselyov demonstrates
[here](http://okmij.org/ftp/continuations/against-callcc.html#traps)
that abstractions built on `call/cc`{.scheme} do not compose.

[^4]: `call-with-prompt` is a bit of a mouthful, so the alias `call/p`
is blessed.

[^5]: There's a working example [here](/static/tasks.lisp) ([with syntax
highlighting](/static/tasks.lisp.html)) as runnable Urn. Clone the
compiler then execute `lua bin/urn.lua --run tasks.lisp`.

<!-- vim: tw=72
-->
