Talk:Haskell/Continuation passing style
From Wikibooks, the open-content textbooks collection
Here's everything I've written for this module so far, before running out of stamina:
Continuation passing style (CPS) in Haskell is, like monads, rather difficult to understand.
What makes a CPS value a CPS value is the fact that it takes an extra parameter, called a continuation. The continuation represents the function being applied to the value: the CPS value simply has to apply the continuation to the value within it. A very simple example:
cps3 cont = cont 3
The consequence of this taking a continuation is a biggie: the CPS value has access to the function being applied to it. This allows values to do weird things: for example, it's easy to make a value that ignores the continuation and returns a different value instead. This is similar to the Maybe monad, where Nothing has the effect of ignoring whatever functions are applied to its "contained value".
The type of a CPS value is simple:
(a -> r) -> r
Here, a represents the CPS value's "inner type". (a -> r) is the function applied to the CPS value, and r is of course the return type of this function.
Note that there is nothing preventing the CPS value from imposing restrictions on r as well as a; a function like const 'a' can be used as a CPS value:
const 'a' :: (a -> Char) -> Char
Since const 'a' ignores any function passed to it, it doesn't matter what this function takes. However, const 'a' will always return a Char, so this must be the function's return type, to "disguise" the fact that the Char is not being pulled from the continuation. This restriction is imposed by the fact that a CPS value has to return the same type as its continuation--even if the continuation is not used.
CPS values aren't often used alone: continuation passing style programming often contains them in great quantities. To make things easier to understand, there's a type with a data constructor that works on CPS values:
newtype Cont r a = Cont { runCont :: ((a -> r) -> r) }
Here r represents the restrictions on return type imposed by the CPS value, and a represents the value contained within the CPS value. The data constructor Cont turns a CPS value into a Cont value, and runCont turns it back again.
Adding Cont to Monad allows us to stitch together continuations:
instance Monad (Cont r) where
return a = Cont $ \k -> k a
(Cont c) >>= f = Cont $ \k -> c (\a -> runCont (f a) k)
First of all, notice that (Cont r), and not Cont, is being defined here as an instance of Monad. This is because it is what (Cont r) is "applied" to that defines what's "contained within" the CPS value.
Now, it's not necessary to understand just what >>= does and how it does it. It just feeds a CPS value into a CPS function and returns a CPS value.
In case you're getting into the "gimme some CODE": we're getting there.
Now, we can define an exit function. It is the same as the usage of const above, except making a Cont value rather than simply a CPS value.
exit x = Cont (const x)
Also, we'll make a finish function that takes the value "inside" a Cont value and returns it:
finish x = runCont x id
This turns its Cont argument into a normal continuation, then applies it to id, ending the computation.
Finally, we'll note that return () acts as a "nop" in any do-block. Here's a basic example of division:
divide x y = finish (do if y == 0
then exit (error "Division by zero")
else return ()
return (x / y))
This does seem very useless, but many imperative constructs can be simulated in the Cont monad.
This leaves the open question: how do I simulate the inarguably imperative monad that is IO? Well, Cont is, in fact, the most general of the monads, in that any monadic value can be turned into a Cont monadic value and back again.
Looking again at the type of >>=:
(>>=) :: m a -> (a -> m b) -> m b
Wait. Did I see a CPS value there? Yep: >>= can be thought of as taking a monadic value and making a CPS value out of it. Simply apply Cont to this value, and we have a fully functional value in the Cont monad!
monadToCPS :: m a -> Cont (m b) a monadToCPS x = Cont (x >>=)
And the inverse:
cpsToMonad :: Cont (m a) a -> m a cpsToMonad x = runCont x return
One nice thing about this is that it will work on any Cont value with the appropriate type: it doesn't have to be one generated by monadToCPS. This allows us to mix other monads with Cont quite easily, though it's against the rules to combine other monads with each other. (Note that cpsToMonad is also the intuitive definition of a finishMonad function, that returns a monadic value instead of "just any value".)
Much stuff. Go insane with it. --Ihope127 18:27, 29 January 2006 (UTC)
[edit] Haskell's self-defeating authors
Why the fixation amongst haskellers on mathematics? I mean, you all couldn't think of a better example for CPS than the quadratic formula? There's simply no need to be adding to the complexity of this, it only serves to steepen the learning curve for us non-math-gurus... seriously...
- I can only encourage you to strengthen your math skills, the quadratic formula is like 9th grade... and a good example, mainly because the "classic" example of error continuations is uncommon in Haskell, since we have
MaybeandEither. - -- apfeλmus 09:32, 17 May 2008 (UTC)
[edit] Error in "a note of typing"
As it appears to me, almost all explanation in this chapter is wrong.
Concerning type of k:
k :: a -> Cont r b
The b type isn't arbitrary because of we don't do anything with result of k. It's arbitrary because we don't do anything with continuation supplied to Cont r b.
Explanation: The return type of k is a Cont Monad which has type signature ((a->r)->r).
As it stands in definition of callCC:
class (Monad m) => MonadCont m where callCC :: ((a -> m b) -> m a) -> m a
instance MonadCont (Cont r) where callCC f = Cont $ \h -> runCont (f (\a -> Cont $ \_ -> h a)) h
As we can see, our k is
(\a -> Cont $ \_ -> h a)
It ignores it's parameter which is of type (a->r) where type of expression is Cont r a
As we can see, because input function is ignored, the "a" type is arbitrary, and thats the real reason for k signature.
Also, the reason we must use "return" at the end of callCC instead of k lies elsewhere.
As we know
x<-m1 m2
really means
m1 >>= (\x->m2) and type of this is m1 :: m a (\x->m2) :: (a -> m b)
Substituting this for our example:
x = Msg :: String m1 = callCC m2 = return (length Msg)
If we use return at the end of callCC, it returns Cont Monad of type Cont r String and it matches type of Msg. If we use k instead of return, type of k is already binded to () by use in "when" so it has type Cont r () and this is mismatch with String in definition of
callCC >>= (Msg -> return (length Msg)).
- I don't quite understand. Strictly speaking, the explanation in the article is correct, if very unclear. The problem is that while the
bink :: a -> Cont r bis indeed arbitrary, you cannot have bothb=Stringandb=()as you point out as well. - The advice of always ending a do-block in
callCCwithreturnis wrong. The proper solution is to givecallCCa rank-3 type:callCC :: ((forall b. a -> Cont r b) -> Cont r a) -> Cont r a
- -- apfeλmus 09:17, 11 September 2008 (UTC)
-
- After reading explanation from article many more times and giving it Deep Thought, I must admit it is correct. Probably my tendency to be very precise and the fact that I was still learning all of that made me unable to understand the text appropriately.
- However, I don't understand why advice about "return" is bad. The type You gave is the same as in actual definition, unless explicit "forall" changes something (what? :).
- -- Spawarotti 13-IX-2008
-
-
- Ah, after some Deep Thought on my part, there are actually two issues here and the advice about
returnis partly right and partly wrong. - To see why
returnis no panacea against type errors withk, consider the following
- Ah, after some Deep Thought on my part, there are actually two issues here and the advice about
-
foo :: Cont r Int
foo = callCC $ \k -> do
x <- k 41
let z = (x + 2) :: Int
when True $ k 42
return 43
-
-
- What type does
khave? The first two lines in the do-block force it to be of typeInt -> Cont r Intwhile the third line forces it to be of typeInt -> Cont r (). In other words, thebfrom the type ofcallCCshould be bothIntand()which is clearly not possible. (Well, unless you use the type I gave with the explicit "forall". Look at its position, it's not just an explicit forall. The chapter Haskell/Polymorphism ought to explain this. Meanwhile, have a look at GHC's Users Guide, Arbitrary rank polymorphism and/or ask a haskell mailing list or #haskell. Basically, you can use severalbnow.) Thereturndidn't help here. - On the other hand, I think the advice was meant to mean that you should not use
kas the last statement, like in
- What type does
-
bar :: Cont r Int bar = callCC $ \k -> do let n = 5 when True $ k n k 25
-
-
- In this case, replacing the final
kwithreturnhelps. But arguably, using the rank-3-type forcallCCis the correct solution in this case as well. - -- apfeλmus 20:51, 16 September 2008 (UTC)
- In this case, replacing the final
-
-
-
-
- Many thanks for Your explanation. That makes everything clear for me :)
- -- Spawarotti 19-IX-2008
-
-

