First, the teaser:
Swarm is a framework allowing the creation of web applications which can scale transparently through a novel portable continuation-based approach. Like Map-Reduce, Swarm follows the maxim “move the computation, not the data”. However Swarm takes the concept much further, allowing it to be applied to almost any computation, not just those that can be broken down into map and reduce operations.
This is pretty cool. It is accomplished using Delimited Continuations with the Scala continuations plugin. Delimited continuations are like a functional version of GOTO statements, but way less evil. They are a way of changing the execution flow of a program, which makes them very powerful but also extremely confusing.
Delimited Continuations Explained (in Scala)
I actually found that link to be pretty helpful, but you need to be stone cold sober to get through it. I wanted to jot down my notes here because otherwise I’ll have to start from the beginning when I try to understand it again. So the rest of this blog post is essentially just a journal entry. Read on if you dare!
Update: Well, that didn’t work. I had to read it again anyway!
In Scala, continuations will be used through two keywords: reset
and shift
. Like catch
and throw
, a shift
always happen inside a reset
, the latter being responsible for delimiting the scope of the continuation.
Wait. What?
reset
is responsible for delimiting the scope of the continuation
Oh, that didn’t clear things up? How about an example:
def foo() = {
println("Once here.")
shift((k: Int => Int) => k(k(k(7))))
}
def bar() = {
1 + foo()
}
def baz() = {
reset(bar() * 2)
}
baz()
// result:
// Once here.
// 70
The CliffsNotes version of how this works:
- The code before
shift
is executed once - The code after
shift
as many times as the functionk
insideshift
is repeated - the code inside the
shift
itself is only executed once, but is interrupted–going back and forth to the code after it (up until the end of thereset
block)
The first thing to recognize is that shifts are functions taking a parameter, and that parameter is another function. In this example, that parameter is the function { k => ... }
. Not only that, but this example defines the type of k
as being Int => Int
, which is itself a function.
When the continuation function k
is executed (from within the shift
block):
- Execution skips over the rest of the
shift
block and begins again at the end of it - Execution continues until the end of the
reset
block - Execution then resumes within the
shift
block after the already-completed execution ofk
until reaching the end of theshift
block again - Execution skips to the end of the
reset
block - Repeat the previous two steps for each call to
k
within theshift
block
So what happens in the above example is:
baz()
is called, which callsbar()
, and thenfoo()
foo()
printsOnce here.
- The
shift
block is entered, which recursively callsk
. The innermost call tok(7)
occurs first, and execution skips to the end of theshift
block. The result is 7. - Now
foo()
is complete and we’re inbar()
, adding 1 to 7 (the result) = 8 - Back in
baz()
, 8 (the result) * 2 = 16 - We reach the end of the
reset
block and resume execution in theshift
block, after the previous execution ofk
. The second call tok
occurs with the current value of 16. Execution again skips to the end of theshift
block and returns tobar()
with this new result of 16 - 1 + 16 = 17
- 17 * 2 = 34
- Reach the end of the
reset
block again and resume at the end of the second call tok
, leading to the third and final call tok
with the current value of 34 - 1 + 34 = 35
- 35 * 2= 70
And the result is:
Once here.
70