1
votes

I'm trying to understand continuation passing and Call-with-current-continuation. As per this page: https://en.wikipedia.org/wiki/Monad_(functional_programming)#Continuation_monad the call with CC method is implemented as follows:

call_cc :: ((a -> (b -> r) -> r) -> (a -> r) -> r) -> (a -> r) -> r
call_cc f k = f (\t x -> k t) k

As described by this signature and implementation.

However, we can see that the x parameter is here never used. Does it mean that any continuation passed to f is always ignored that the initial continuation k is always replacing it? In that case, does it mean that call-with-cc can only ever call a function with that is one-level deep and not more? (because the next function that would be called in a normal control flow with a continuation x is ignored)

In that case it seems very limiting, what is its practical use?

2

2 Answers

1
votes

Here you can find more readable definition of callCC with very specific explanation:

class (Monad m) => MonadCont m where 
    callCC :: ((a -> m b) -> m a) -> m a 

The MonadCont class provides the callCC function, which provides an escape continuation mechanism for use with Continuation monads. Escape continuations allow you to abort the current computation and return a value immediately. They achieve a similar effect to throwError and catchError within an Error monad.

Here you can see some pretty examples how to use it.

But in general: callCC does not ignore input, but it enhances your procedure with way to do it.

You are defining some function like

doSomething panicExit = do
  ...
  when failCondition $ panicExit inputToStartOver
  ...

And then converting to normal continuation via callCC doSomething

And exactly this panicExit input-ignoring throw-like has caught your eye. You procedure is not obliged to fail to default value, but you can do it when you like.

1
votes

(For the sake of exposition, let me use more concise syntax rather than real Haskell.)

callcc f applies the given function f to the current continuation k. If this continuation k is ever invoked under another continuation x, the latter continuation x is indeed discarded.

For instance, in 1 + callcc (\k -> 2 + (k 3)), the variable k is bound to the outer continuation 1 + [] where [] is a hole into which a return value shall be filled. When this k is applied as in (k 3), the inner continuation 1 + (2 + []) is discarded. As a result, the entire expression becomes 1 + 3 and evaluates to 4.

On the other hand, in 1 + callcc (\k -> 2 + 4), the continuation k is never invoked and the whole expression yields 1 + (2 + 4), that is, 7.

By combining the two examples above, you can do more complicated things like 1 + callcc (\k -> 2 + (if some_complex_condition then (k 3) else 4)), which gives 1 + 3 if some_complex_condition is true, or 1 + (2 + 4) otherwise.

To sum up and answer your question:

However, we can see that the x parameter is here never used.

x is discarded if the continuation k is invoked; however, when k is not invoked, x is applied to the "normal" return value, as in the examples above. (The latter case corresponds to the return operation in the continuation monad.)

P.S.

Above I adopted a direct, non-monadic syntax for brevity. Using the continuation monad in Haskell, the following example

callCC (\k -> do r <- (k 3); return (2 + r))

invokes the outer continuation k discarding the inner 2 + [] and returns 3, while

callCC (\k -> do r <- (return 4); return (2 + r))

gives 2 + 4.