I usually like to think of delimited continuations from the inside. First a haskell example because there I can heap on syntax sugar:
do
x
let's look at bar:
bar x = Cont (\fr -> ...)
Or without the type wrapper:
bar x fr = ...
x represents the environment - all variables that are in scope and can be used to compute the next step. They represent everything that came before bar.
fr are all continuations that come after us, reified as a function. We can use the type of bar
bar :: env -> (next-> result) -> result
to find ways to use continuations. Time for type tetris!
Easiest way to get a result value is to calculate a next value from env. But we can do much more fun things as well, like implementing control jumps. Lets look at how we can implement this jumping:
jumpCont env _ignoredRestOfBlock = jumpTarget (...env)
We ignore the function that represents the rest of our current block and use something else instead. Now we just have to figure out what 'something else' might be:
callCC comnputeInnerBlock = \_ignoredEnv jumpTarget -> comnputeInnerBlock jumpCont
where jumpCont env _ignoredRestOfBlock = jumpTarget env
So we have two streams of control - the one callCC is part of and one nested within callCC. If we call jumpCont we ignore the rest of the nested block and continue with the callCC control stream, exiting the current block:
callCC $ \exitBlock -> do
x
So if normal functions take the result of what came before as an argument and compute a new result, continuations take the result of what came before and a function that represents the rest of the computation and compute a new result. You can think of this function as created for you by some compiler magic.