public
Created

Example of Scala continuations used with asynchronous programming

  • Download Gist
gistfile1.scala
Scala
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
scala -P:continuations:enable
 
// Simulate a callback handled by the "system"
var myCallback: Int => Unit = null
 
// Simulate a non-blocking system read function. It returns directly after registering the callback.
def read(callback: Int => Unit): Unit = myCallback = callback
 
// First, without continuations, you must write things in a funny style, even with Scala's lightweight syntax.
// Note how each callback typically causes a new level of indentation.
read { foo =>
println("foo = " + foo)
read { bar =>
println("foo + bar = " + (foo + bar))
}
}
 
// Now here is the same program in imperative style.
// Here the reset block returns to the system "right away" (with a Unit type because read() returns Unit).
import scala.util.continuations._
 
reset {
val foo = shift(read)
println("foo = " + foo)
val bar = shift(read)
println("foo + bar = " + (foo + bar))
}
 
// Now imagine the system finds out that some data is available and that there is a callback ready.
// The system now calls the callback:
// - This causes shift to evaluate to a value and the code after the shift to run
// - The code "suspends" again at the second shift, with a new callback registered.
myCallback(42)
foo = 42
 
// Some new data is available again.
// The system now calls the new callback:
// - This causes the second shift to evaluate to a value and the code after that shift to run
// - The code runs until the end of the `reset` block
// - We are done!
myCallback(43)
foo + bar = 85
 
// This even works with while() loops
// Because in this use of continuations control unwinds the stack back to the top, there is no stack explosion
reset {
var count = 0
var value = -1
while (count < 1000000) {
value = shift(read)
count += 1
println(value + " " + count)
}
println("done")
}
 
(1 to 1000000) foreach (_ => myCallback(42))

This is an example showing how Scala continuations can be useful to implement a system with asynchronous events and/or I/O, very much like the current proposal for JavaScript deferred functions.

Here we simulate a single-threaded "system" (à la Node.js) which runs your application code and provides a non-blocking API. Here, the API is just one function, read(), which reads an int from some external source.

The application code is supposed to run quickly and to not block at all. The sooner it terminates, the better. Here what it means is that the code with reset (and after reset) should run quickly and then return to the system.

The example application code here reads two values in sequence using the read() function.

If we write it without continuations, the programming style is not very natural: even with Scala's pretty lightweight syntax for closures, you will typically nest callbacks within each other, and add some amount of boilerplate.

Introduce continuations with reset and shift, and voilà: the code now looks imperative (as in "step by step") again, while in fact, behind the scene, it never blocks on read() and still uses callbacks.

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.