I just finished Programming in Scala, and I've been looking into the changes between Scala 2.7 and 2.8. The one that seems to be the most important is the continuations plugin, but I don't understand what it's useful for or how it works. I've seen that it's good for asynchronous I/O, but I haven't been able to find out why. Some of the more popular resources on the subject are these:
- Delimited continuations and Scala
- Goto in Scala
- A Taste of 2.8: Continuations
- Delimited Continuations Explained (in Scala)
And this question on Stack Overflow:
Unfortunately, none of these references try to define what continuations are for or what the shift/reset functions are supposed to do, and I haven't found any references that do. I haven't been able to guess how any of the examples in the linked articles work (or what they do), so one way to help me out could be to go line-by-line through one of those samples. Even this simple one from the third article:
reset {
...
shift { k: (Int=>Int) => // The continuation k will be the '_ + 1' below.
k(7)
} + 1
}
// Result: 8
Why is the result 8? That would probably help me to get started.
Scala Continuations via Meaningful Examples
Let us define
from0to10
that expresses the idea of iteration from 0 to 10:Now,
prints:
In fact, we do not need
x
:prints the same result.
And
prints all pairs:
Now, how does that work?
There is the called code,
from0to10
, and the calling code. In this case, it is the block that followsreset
. One of the parameters passed to the called code is a return address that shows what part of the calling code has not yet been executed (**). That part of the calling code is the continuation. The called code can do with that parameter whatever it decides to: pass control to it, or ignore, or call it multiple times. Herefrom0to10
calls that continuation for each integer in the range 0..10.But where does the continuation end? This is important because the last
return
from the continuation returns control to the called code,from0to10
. In Scala, it ends where thereset
block ends (*).Now, we see that the continuation is declared as
cont: Int => Unit
. Why? We invokefrom0to10
asval x = from0to10()
, andInt
is the type of value that goes tox
.Unit
means that the block afterreset
must return no value (otherwise there will be a type error). In general, there are 4 type signatures: function input, continuation input, continuation result, function result. All four must match the invocation context.Above, we printed pairs of values. Let us print the multiplication table. But how do we output
\n
after each row?The function
back
lets us specify what must be done when control returns back, from the continuation to the code that called it.back
first calls its continuation, and then performs the action.It prints:
Well, now it's time for some brain-twisters. There are two invocations of
from0to10
. What is the continuation for the firstfrom0to10
? It follows the invocation offrom0to10
in the binary code, but in the source code it also includes the assignment statementval i =
. It ends where thereset
block ends, but the end of thereset
block does not return control to the firstfrom0to10
. The end of thereset
block returns control to the 2ndfrom0to10
, that in turn eventually returns control toback
, and it isback
that returns control to the first invocation offrom0to10
. When the first (yes! 1st!)from0to10
exits, the wholereset
block is exited.Such method of returning control back is called backtracking, it is a very old technique, known at least from the times of Prolog and AI-oriented Lisp derivatives.
The names
reset
andshift
are misnomers. These names should better have been left for the bitwise operations.reset
defines continuation boundaries, andshift
takes a continuation from the call stack.Note(s)
(*) In Scala, the continuation ends where the
reset
block ends. Another possible approach would be to let it end where the function ends.(**) One of the parameters of the called code is a return address that shows what part of the calling code has not yet been executed. Well, in Scala, a sequence of return addresses is used for that. How many? All of the return addresses placed on the call stack since entering the
reset
block.UPD Part 2 Discarding Continuations: Filtering
This prints:
Let us factor out two important operations: discarding the continuation (
fail()
) and passing control on to it (succ()
):Both versions of
succ()
(above) work. It turns out thatshift
has a funny signature, and althoughsucc()
does nothing, it must have that signature for type balance.as expected, it prints
Within a function,
succ()
is not necessary:again, it prints
Now, let us define
onOdd()
viaonEven()
:Above, if
x
is even, an exception is thrown and the continuation is not called; ifx
is odd, the exception is not thrown and the continuation is called. The above code prints: