This writeup assumes you have some familiarity with continuations. The Responder class and the examples are by Martin Odersky, althought the continuation monad in Scala has been known for some time before. These comments by Burak Emir (but feel free to add/change).
For easy reference, here is the core code from the scala.Responder class and singleton object
object Responder { def constant[A](x: A) = new Responder[A] { def respond(k: A => Unit) = k(x) } def exec[A](x: => Unit): Boolean = { x; true } def run[A](r: Responder[A]): Option[A] = { var result: Option[A] = None r.foreach(x => result = Some(x)) result } def loop[A](r: Responder[Unit]): Responder[Nothing] = for (_ <- r; val y <- loop(r)) yield y def loopWhile[A](cond: => Boolean)(r: Responder[Unit]): Responder[Unit] = if (cond) for (_ <- r; val y <- loopWhile(cond)(r)) yield y else constant(()) } abstract class Responder[+A] { def respond(k: A => Unit): Unit def foreach(k: A => Unit) { respond(k) } def map[B](f: A => B) = new Responder[B] { def respond(k: B => Unit) { Responder.this.respond(x => k(f(x))) } } def flatMap[B](f: A => Responder[B]) = new Responder[B] { def respond(k: B => Unit) { Responder.this.respond(x => f(x).respond(k)) } } def filter(p: A => Boolean) = new Responder[A] { def respond(k: A => Unit) { Responder.this.respond(x => if (p(x)) k(x) else ()) } } }
Some explanations for object Responder:
Some explanations for class Responder:
Some explanations on flatMap and for-comprehensions
It’s probably easiest to understand how responders relate to continuations by solving an IO-problem in Scala: a dialogue, on a terminal, with the possible to go back and correct answers. For this, we’ll make use of a Reader class and a Reader object.
The Reader object will have a field containing a list of pairs (r,k) of a reader and a continuation. The idea is after each succesful input, it will “dump its stack”. This is not really practical if many of these dialogues are going to be used in your program, but it’s only a toy example and writing a reset routine is left as an exercise.
What goes in object Reader? A way to ask questions to the user. It constructs an instance of class Reader, which is a Responder that (quite lazily) gets the user input when presented with the remaining program (the continuation). If the users says “back”, it just grabs the last reader from the list, and then passes the continuation to that one.
object Readers { private var readers: List[Pair[Reader, String => unit]] = List() def ask(prompt: String) = new Reader(prompt) class Reader(prompt: String) extends Responder[String] { def respond(k: String => unit): unit = { print("["+(readers.length+1)+"]"+prompt+"> "); val line = readLine; if (line == "back") { val Pair(rdr, k) = readers.head; readers = readers.tail rdr.respond(k) } else { readers = Pair(this, k) :: readers k(line) } } } }
Now we can use our Readers to implement a dialogue that allows to go back. Keeping it no simpler than possible, the dialogue is asking for two numbers to be added, the first of which needs to be positive.
import Responder._ import Readers._ object Test1 extends Application { run { for { val line1 <- ask("first number") val f = Integer.parseInt(line1) f > 0 exec(println("first number parsed!")) val s <- ask("second number") map Integer.parseInt } yield f + s } match { case Some(x) => println("the sum is "+x) case None => println("(aborted)") } }
import Responder._ import Readers._ import Console._ object Test2 extends Application { def sumup(x: int): Responder[int] = for { val line <- ask("enter a number"); val rest <- if (line == "") constant(x) else if (line == "?") { println("sum so far: " + x); sumup(x) } else sumup(x + Integer.parseInt(line)) } yield rest println("sum = " + run(sumup(0)).get) }