If shift
is called outside of a delimiting reset
block, it can be used to create functions that themselves create continuations inside a reset
block. It is important to note that shift
's type is not just (((A => B) => C) => A)
, it is actually (((A => B) => C) => (A @cpsParam[B, C]))
. That annotation marks where CPS transformations are needed. Functions that call shift
without reset
have their return type "infected" with that annotation.
Inside a reset
block, a value of A @cpsParam[B, C]
seems to have a value of A
, though really it's just pretending. The continuation that is needed to complete the computation has type A => B
, so the code following a method that returns this type must return B
. C
is the "real" return type, and after CPS transformation the function call has the type C
.
Now, the example, taken from the Scaladoc of the library
val sessions = new HashMap[UUID, Int=>Unit]
def ask(prompt: String): Int @suspendable = // alias for @cpsParam[Unit, Unit]. @cps[Unit] is also an alias. (@cps[A] = @cpsParam[A,A])
shift {
k: (Int => Unit) => {
println(prompt)
val id = uuidGen
sessions += id -> k
}
}
def go(): Unit = reset {
println("Welcome!")
val first = ask("Please give me a number") // Uses CPS just like shift
val second = ask("Please enter another number")
printf("The sum of your numbers is: %d\n", first + second)
}
Here, ask
will store the continuation into a map, and later some other code can retrieve that "session" and pass in the result of the query to the user. In this way, go
can actually be using an asynchronous library while its code looks like normal imperative code.