11
votes

I'm trying to use Scala (2.9.0) continuations to build a seemingly blocking API, but that actually is asynchronous. Suppose that you would like to write something like:

if(ask("Continue?")) //Prompts Yes/No
  name = input("Enter your name")

Where ask returns a boolean if the user pressed yes, and input asks for a value. Picture this being called from a web server, where ask and input do not block any threads, they just store a continuation in a Map (or the session, doesn't matter much) before displaying the page with the prompt (releasing most resources). And when a response get's back, it looks-up the continuation in the Map and resumes the code.

The problem so far is that I cannot seem to be able to find a suitable way to define ask and input to use continuations without passing the calling context's return type as a parameter.

The closest I got is doing something like:

#!/bin/sh
exec scala -P:continuations:enable -deprecation "$0" "$@"
!#
import util.continuations._

//Api code
def display[T](prompt: String) = shift {
  cont: (Unit => T) => {
        println(prompt)
        cont()
    }
}

//Client code
def foo() : Int = reset {
  display[Int]("foo!") // <-- how do I get rid of the type annotation?
  5
}

def bar() : Unit = reset {
  display[Unit]("bar!")
}

println(foo())
bar()

I really would like to get rid of the type annotation on calls to display. Does anyone know of a way of achieving this? I don't care if the API definition gets uglier, as long as the client code gets simpler. Thanks!

1
Post your answer as an answer.Daniel C. Sobral

1 Answers

6
votes

I finally figured it out:

#!/bin/sh
exec scala -P:continuations:enable -deprecation "$0" "$@"
!#
import util.continuations._

class Display(val resume: (Unit => Any)) extends Throwable

//Api code
def display(prompt: String) = shift {
  cont: (Unit => Any) => {
        println(prompt)
        throw new Display(cont)
    }
}

//Client code
def foo() : Int = reset {
  display("foo!")
  5
}

def bar() : Unit = reset {
  display("bar!")
}

//Framework
try {
    foo()
} catch {
    case d: Display => println(d.resume())
}

try {
    bar()
} catch {
    case d: Display => d.resume() 
}

The trick is accepting methods that return Any (Homeresque: D'oh!) and returning Nothing.

If you want to implement something that returns a value, such as ask, you can do:

class Ask(val resume: (Boolean => Any)) extends Throwable

//Api code
def ask(prompt: String) = shift {
  cont: (Boolean => Any) => {
        println(prompt)
        throw new Ask(cont)
    }
}

In the above code, ask returns a Boolean.