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.
I found the existing explanations to be less effective at explaining the concept than I would hope. I hope this one is clear (and correct.) I have not used continuations yet.
When a continuation function
cf
is called:shift
block and begins again at the end of itcf
is what theshift
block "evaluates" to as execution continues. this can be different for every call tocf
reset
block (or until a call toreset
if there is no block)reset
block (or the parameter toreset
() if there is no block) is whatcf
returnscf
until the end of theshift
blockreset
block (or a call to reset?)So in this example, follow the letters from A to Z
This prints:
Continuation capture the state of a computation, to be invoked later.
Think of the computation between leaving the shift expression and leaving the reset expression as a function. Inside the shift expression this function is called k, it is the continuation. You can pass it around, invoke it later, even more than once.
I think the value returned by the reset expression is the value of the expression inside the shift expression after the =>, but about this I'm not quite sure.
So with continuations you can wrap up a rather arbitrary and non-local piece of code in a function. This can be used to implement non-standard control flow, such as coroutining or backtracking.
So continuations should be used on a system level. Sprinkling them through your application code would be a sure recipe for nightmares, much worse than the worst spaghetti code using goto could ever be.
Disclaimer: I have no in depth understanding of continuations in Scala, I just inferred it from looking at the examples and knowing continuations from Scheme.
Given the canonical example from the research paper for Scala's delimited continuations, modified slightly so the function input to
shift
is given the namef
and thus is no longer anonymous.The Scala plugin transforms this example such that the computation (within the input argument of
reset
) starting from eachshift
to the invocation ofreset
is replaced with the function (e.g.f
) input toshift
.The replaced computation is shifted (i.e. moved) into a function
k
. The functionf
inputs the functionk
, wherek
contains the replaced computation,k
inputsx: Int
, and the computation ink
replacesshift(f)
withx
.Which has the same effect as:
Note the type
Int
of the input parameterx
(i.e. the type signature ofk
) was given by the type signature of the input parameter off
.Another borrowed example with the conceptually equivalent abstraction, i.e.
read
is the function input toshift
:I believe this would be translated to the logical equivalent of:
I hope this elucidates the coherent common abstraction which was somewhat obfuscated by prior presentation of these two examples. For example, the canonical first example was presented in the research paper as an anonymous function, instead of my named
f
, thus it was not immediately clear to some readers that it was abstractly analogous to theread
in the borrowed second example.Thus delimited continuations create the illusion of an inversion-of-control from "you call me from outside of
reset
" to "I call you insidereset
".Note the return type of
f
is, butk
is not, required to be the same as the return type ofreset
, i.e.f
has the freedom to declare any return type fork
as long asf
returns the same type asreset
. Ditto forread
andcapture
(see alsoENV
below).Delimited continuations do not implicitly invert the control of state, e.g.
read
andcallback
are not pure functions. Thus the caller can not create referentially transparent expressions and thus does not have declarative (a.k.a. transparent) control over intended imperative semantics.We can explicitly achieve pure functions with delimited continuations.
I believe this would be translated to the logical equivalent of:
This is getting noisy, because of the explicit environment.
Tangentially note, Scala does not have Haskell's global type inference and thus as far as I know couldn't support implicit lifting to a state monad's
unit
(as one possible strategy for hiding the explicit environment), because Haskell's global (Hindley-Milner) type inference depends on not supporting diamond multiple virtual inheritance.From my point of view, the best explanation was given here: http://jim-mcbeath.blogspot.ru/2010/08/delimited-continuations.html
One of examples:
Another (more recent -- May 2016) article on Scala continuations is:
"Time Travel in Scala: CPS in Scala (scala’s continuation)" by Shivansh Srivastava (
shiv4nsh
).It also refers to Jim McBeath's article mentioned in Dmitry Bespalov's answer.
But before that, it describes Continuations like so:
That being said, as announced in April 2014 for Scala 2.11.0-RC1
My blog does explain what
reset
andshift
do, so you may want to read that again.Another good source, which I also point in my blog, is the Wikipedia entry on continuation passing style. That one is, by far, the most clear on the subject, though it does not use Scala syntax, and the continuation is explicitly passed.
The paper on delimited continuations, which I link to in my blog but seems to have become broken, gives many examples of usage.
But I think the best example of the concept of delimited continuations is Scala Swarm. In it, the library stops the execution of your code at one point, and the remaining computation becomes the continuation. The library then does something -- in this case, transferring the computation to another host, and returns the result (the value of the variable which was accessed) to the computation that was stopped.
Now, you don't understand even the simple example on the Scala page, so do read my blog. In it I'm only concerned with explaining these basics, of why the result is
8
.