Continuations that store the heap?

I have generally understood a continuation as being more or less a copy of a call stack, and that it leaves the state of the heap untouched. I was wondering is if it restored the heap to its original state, would it still be considered a continuation?

Thanks in advance!

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

informal semantics description

My answer addresses semantics and uses practical ideas, not theory. (I don't care about theory.) Since you want practical consequences on a heap, this view might be helpful.

cdiggins: I have generally understood a continuation as being more or less a copy of a call stack, and that it leaves the state of the heap untouched.

A continuation has both state and behavior parts. The state part typically looks like the "stack" for a current thread of execution. It's almost but not quite thread state. But since you can cause a thread to run more than one continuation, serially and interleaved in some fashion, it's wrong to think of a continuation as identical to a thread. Instead, a continuation is what a thread might be doing if it runs. At any given moment, a thread runs at most one continuation. Think of continuations as halfway to fibers (lightweight threads). That's why you can implement OS-like context switching in a language with first class continuations.

The difference between thread and continuation is more interesting in a language whose call stack is actually heap-based, using so-called cactus stacks or spaghetti stacks, where call trees might be physically modelled by linked lists of heap-based activation frames. In languages like C where "the" stack for a thread is typically monolithic and singular instead of fragmented and legion, it's hard to separate ideas of continuation and stack.

If a thread is suspended for any reason, say because blocked by an operating system, then a continuation is what the thread is going to do next. It's the state and register set (etc) one puts back to resume a thread. Such OS thread state isn't typically exposed as a first class type to a programming language (unless you squint while looking at setjmp() and longjmp() in C). When you do expose a continuation as a data type in a language, instead of making it possible to resume control at all possible points of interruption, typically you only allow certain well-defined points in code flow to become continuations. For example, a function's "return address" (and associated stack) in the caller is a good place to let a continuation become visible as a first class object, because languages are often already invested in heavyweight call-tree bookkeeping (which is what can make function calls expensive compared to inline code).

A continuation, as a first class object, is a callable thing which causes the current thread to resume running where it left off when the continuation was captured. However, both stack and heap can change after a continuation is created. And both stack and heap can change each time a continuation is called. You can think of a continuation as similar to a closure with a wilder effect of control flow discontinuity, like exception throwing, except calling a continuation can go either up or down the stack of a call-tree. You might have heard folks say objects are a poor-man's closure, or vice versa. When you have a closure, you can modify state in a closure without making it stop being a closure. Similarly, you can modify state in a continuation's stack without making it stop being a continuation. And the heap can change arbitrarily. So I don't think it's correct to say a heap is untouched; rather, a heap is outside the scope of a continuation, and can change independently.

The behavior part of a continuation is a bit like that of a function. Except each time you call a function you have a new caller, and you get a new activation frame for the function. But when you call a continuation, you get an old caller, with activation frames that existed when a continuation was created. It's like going back in time -- except both stacks and heaps can change between continuation calls, so it's useful and meaningful to call a continuation again, since the world has changed since the last call. It's the "traveling back in time" aspect that makes a casual specification of continuations sound so weird when looking at examples.

cdiggins: I was wondering is if it restored the heap to its original state, would it still be considered a continuation?

In a language with side effects, if a developer carefully arranges specific side effects to occur altering heap-based state, it sounds like restoring a heap to its original state would subvert a developer's plans. A continuation that restores a heap would not look like a continuation to a developer who thinks both stack and heap can change independently.

thanks

Thanks for the additional clarity regarding the semantics of continuations with regards to threads.

In a language with side effects, if a developer carefully arranges specific side effects to occur altering heap-based state, it sounds like restoring a heap to its original state would subvert a developer's plans.

Yes I agree if the developer is expecting typical continuation semantics. I can see other uses if a continuation restores the heap. For example if this kind of "continuation" can be serialized we can imagine undo buffers or load/save facilities that are trivial to implement.

I imagine this as being a complement to traditional continuations.

A continuation that restores a heap would not look like a continuation to a developer who thinks both stack and heap can change independently.

Yes, I agree. So I am wondering what to call such a construct if it isn't really a continuation.

For more information, I am implementing a VM, and I have an opcode for storing the entire state of the VM (including the heap) as an object on the stack.

Probably a too wishful opcode

Given future architectures, that opcode would be a nightmare for backward compatibility. You would need a global lock to provide a consistent snapshot of the VM state.

You would need a global

You would need a global lock to provide a consistent snapshot of the VM state.

Just like when serializing any global application state (e.g. File > Save).

external states?

If you save the entire state of the process, then any path which leads from call/cc to continuation invocation will loop infinitely, methinks.

Some state is difficult to save though: RNG seeds, ports, etc. For these snapshots to be useful, it must be your intention to do the "same thing" over again with different world-state. What are you looking at doing?

Uses

Call cdiggin's primitive 'restore/cc' so we have a name for it. It couples the call/cc with the restoration. Assume it creates a continuation that captures the state from just after the restore/cc was invoked, so there are no issues with looping.

One point to realize is that the restore-continuation can receive an arbitrary, complex value from the recently 'dead' world. This could contain advice, like "see this, don't do this!". It could even contain a new restore/cc continuation that restores the now 'dead' world. Where call/cc forks the stack allowing multiple lightweight threads, you could essentially use restore/cc to fork the process and rotate between lightweight processes.

It's a very flexible idea. You get undo, redo, backtracking, process forking, et cetera. The actual implementation can use copy-on-write techniques, so as to be fairly lightweight unless you write a lot.

RNG state is easy enough to save (it can easily be part of the heap or global space). Ports, file handles, and other external state would be in the "know what you're doing" category. With my interest in distributed programming this isn't something I'd want for my language, but I can see how it could be useful.

Good point regarding the

Good point regarding the argument. That does indeed make things interesting!

Why not distributed computing?

With my interest in distributed programming this isn't something I'd want for my language

I don't know the details of your language, but I thought that the ability to easily pack up a VM's state and then serialize it across a network could be useful for distributed computing applications.

Opposite problem

With distributed programming languages, the VM essentially is the entire network.

First-class Undo, Backtracking, Worlds

I am wondering what to call such a construct if it isn't really a continuation.

The feature seems related to First-class Undo or Worlds or Backtracking.

First class stores

Great threads, thanks!

One paper really jumps out at me: Refining First Class Stores by Greg Morrisett (Citeseer entry).

A first-class store is an object that captures the values of mutable objects at a particular time in a program's execution. First-class stores allow programmers to cleanly, safely, and efficiently implement "undo" and "redo" operations on mutable objects.

This sounds most like what I am exploring.