Scala-arm can make use of delimited continuations to simplify code. A delimited continuation is a way of rewriting code to simplify continuation passing style. The essence of delmited continuations is contained Scala’s ControlContext
class.
Essentially, a ControlContext is a way of storing computation that has occured and computation that will occur later. Let’s look at an example:
In this example, the code val input = managed(new FileInputStream('test.txt")) !
makes a call to scala.util.continuations.shift
. This places a portion of computation into a ControlContext
. The beginning of this computation opens the file input.txt
and the end of the computation closes the file input.txt
. The middle portion is left empty to be filled in later via a continuations.
The result type of the now
operator on continuations is @suspendable which is shorthand for @cpsParamUnit,Unit. This captures the type of each portion of computation in the ControlContext
. The type InputStream @cpsParam[Unit,Unit]
denotes that the computation so far generates an InputStream
, requires a continuation that takes the InputStream
and returns a Unit and will eventually return a Unit
when completed.
ControlContext
is nestable. When constructing a ControlContext
inside of another one, the ending computations must line up. Let’s look at an example with nested managed resources.
In this example, the val output = managed(new FileOutputStream('test2.txt")) !
expression is nested inside the outer delimited continuation expression. This causes the opening of test2.txt
to happen after the opening of test.txt
and the closing of test2.txt
to happen before the closing of test.txt
.
The expression also creates a new nested ControlContext
with type FileOutputStream @cpsParam[Unit,Unit]
. The last parameter of the computation must line up with the second parameter of the outer ControlContext
so that the computations can be nested. There is still a ‘hole’ left in the middle of the entire computation for the remaining block.
The below code implements an echo server that listens on a port and echos back every full line of text it receives.
import java.io._
import util.continuations._
import resource._
def each_line_from(r : BufferedReader) : String @suspendable =
shift { k =>
var line = r.readLine
while(line != null) {
k(line)
line = r.readLine
}
}
reset {
val server = managed(new ServerSocket(8007)) !
while(true) {
// This reset is not needed, however the below denotes a "flow" of execution that can be deferred.
// One can envision an asynchronous execuction model that would support the exact same semantics as below.
reset {
val connection = managed(server.accept).now
val output = managed(connection.getOutputStream).now
val input = managed(connection.getInputStream).now
val writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(output)))
val reader = new BufferedReader(new InputStreamReader(input))
writer.println(each_line_from(reader))
writer.flush()
}
}
}
The final computation placed in the hole left over from all the nested ControlContext
s is the simple:
writer.println(_)
writer.flush()
You can see how each call to shift
(either the now
operator or the each_line_from
method) causes additional computation before and after the ‘hole’. You can also see in each_line_from
how that ‘hole’ in the computation can be used more than once to complete the entire process.
Delimited continuations provide a lot of power. This simple model of thinking about them helps understand the type signatures and how to use them effectively.