Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
branch: master

Fetching latest commit…

Octocat-spinner-32-eaf2f5

Cannot retrieve the latest commit at this time

..
Octocat-spinner-32 Intro.scala
Octocat-spinner-32 README.md
README.md

Introduction to Delimited Continuations

7 July 2012

Consider the following program:

def run() = {
  println("What is your name?")
  val name = readln()
  println("Hello, " + name + "!")
}

We've all written this kind of program when we were first learning to code. It turns out to be a good place to start learning about continuations!

What's really going on with this program?

  1. A message is printed to the console
  2. A line is read from the console
  3. Another message is printed to the console

When the program reads a line from the console, something interesting has happened. The flow of the program has been interrupted, and it cannot proceed until the user has typed a line of text. The rest of the program is now a function which takes a String and prints a message to the console, and is invoked only when the user has provided input.

The "rest of the program" is a continuation, and looks like this:

val k = { name: String => println("Hello, " + name + "!") }

The run method can be rearranged to invoke the continuation once the user has typed a line of text.

def run() = {
  println("What is your name?")
  k(readln())
}

Let's rewrite run using Scala's delimited continuations primitives, shift and reset.

def run() = reset {
  println("What is your name?")
  val name = shift { k: (String => Unit) => 
    val name = readln()
    k(name)
  }
  println("Hello, " + name + "!")
}

The flow of the program has not changed. Printing the second message to the screen becomes a continuation of type String => Unit, which is passed into the shift block as the function k, to be invoked with the user's input.

Now we can start to do some neat things with the continuation. There's no reason that k has to be invoked from within the shift block. Maybe we don't want to wait for the user to input a line of text, or maybe we don't even know when or from where the input will come. In either case, we can put the continuation off to the side, so we can invoke it by some other means.

var c: String => Unit = _

def run() = reset {
  println("What is your name?")
  val name = shift { k: (String => Unit) =>
    c = k
  }
  println("Hello, " + name + "!")
}

Invoking run will stick the continuation (which in this case is everything after the shift block -- a function which takes a String and prints a message to the console) into the variable c, which we can invoke later (eg from the REPL, another function, etc.).

The shift block is somewhat tricky to read, and interrupts the visual interpretation of what the run method is intended to do. To clean things up, it can be refactored into a more descriptive method, prompt.

var c: String => Unit = _

def prompt() = shift { k: (String => Unit) => c = k }

def run() = reset {
  println("What is your name?  ** call c(<your name>) to continue **")
  val name = prompt()
  println("Hello, " + name + "!")
}

It is easy to read the run method and have a general idea of what it does, accepting that we're not specific on how prompt prompts for user input.

The imperative-looking prompt method can be used to cleanly embed many shift blocks in the run method, so we can have multiple-step asynchronous user workflows.

var c: String => Unit = _

def prompt() = shift { k: (String => Unit) => c = k }

def run() = reset {
  println("What is your name?  ** call c(<your name>) to continue **")
  val name = prompt()
  println("Hello, " + name + "!")

  println("How old are you?  ** call c(<your age>) to continue **")
  val age = prompt()
  println("You are " + age + " years old, " + name + "!")

  println("Where do you live?  ** call c(<your town>) to continue **")
  val town = prompt()
  println("You are " + age + " years old and live in " + town + ", " + name + "!")
}

Running this in the REPL looks like this:

scala> run()
What is your name?  ** call c(<your name>) to continue **

scala> c("James")
Hello, James!
How old are you?  ** call c(<your age>) to continue **

scala> c("29")
You are 29 years old, James!
Where do you live?  ** call c(<your town>) to continue **

scala> c("Palo Alto")
You are 29 years old and live in Palo Alto, James!

scala>
Something went wrong with that request. Please try again.