Imperative Web workflows via delimited continuations

James Earl Douglas

July 17, 2012

HelloWorld++

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

Look Ma, a continuation!

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

Pull it aside

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

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

Continuation-passing style

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

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

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 + "!")
}

Procrastinate

var c: String => Unit = _

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

Do it live

scala> run()
What is your name?

scala> c("James")
Hello, James!

scala>

This code sure is ugly

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

var c: String => Unit = _

Pull aside the shift

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

var c: String => Unit = _

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

Now we can cleanly prompt twice

var c: String => Unit = _

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

def run() = reset {
  println("What is your name?)
  val name = prompt()
  println("How old are you?)
  val age = prompt()
  println("Hello, " + name + "!")
  println("You are " + age + " years old.")
}

Two continuations in one

var c: String => Unit = _

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

def run() = reset {
  println("What is your name?)
  val name = prompt()
  println("How old are you?)
  val age = prompt()
  println("Hello, " + name + "!")
  println("You are " + age + " years old.")
}

Back to the REPL

scala> run()
What is your name?

scala> c("James")
How old are you?

scala> c("29")
Hello, James!
You are 29 years old.

scala>

Clear, imperative workflow

def run() = {
  println("What is your name?")
  val name = readln()
  println("How old are you?")
  val age = readln()
  println("Hello, " + name + "!")
  println("You are " + age + " years old.")
}

Replace the terminal with a browser

def run(): NodeSeq = reset {
  val name = prompt(<input name="Name" />)
  val age  = prompt(<input name="Age" />)
  <h1>Hello { name }!<h1>
  <p>You are { age } years old.</p>
}

Our familiar double-continuation

def run(): NodeSeq = reset {
  val name = prompt(<input name="Name" />)
  val age  = prompt(<input name="Age" />)
  <h1>Hello { name }!<h1>
  <p>You are { age } years old.</p>
}

Our familiar double-continuation

def run(): NodeSeq = reset {
  val name = prompt(<input name="Name" />)
  val age  = prompt(<input name="Age" />)
  <h1>Hello { name }!<h1>
  <p>You are { age } years old.</p>
}

var step: (String => NodeSeq) = _ => run()

def prompt(html: NodeSeq): String @imp =
  shift { k: (String => NodeSeq) =>
    step = k
    html
  }

Scalatra kicks things off

get("/") { step(params("input")) }

More complex forms

def getNameAndAge(
  params: Map[String, String] = Map.empty):
  (String, String) @imp = { ... }

def getBook(
  params: Map[String, String] = Map.empty):
  String @imp = { ... }

def run(): NodeSeq = reset {
  val (name, age) = getNameAndAge()
  val book = getBook()
  <h1>Hello { name }!<h1>
  <p>You are { age } years old.</p>
  <p>Your favorite book is { book }.</p>
}

Validation and re-prompting

def getBook(
  params: Map[String, String]):
  String @imp =
    params.get("Book") match {
      case None => prompt(...)
      case Some(book) => book
    }

Demo

State encoded in the continuation

def getItems(
  params: Map[String, String] = Map.empty,
  cart: List[Item] = Nil):
  List[Item] @imp = { ... }

^D