Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

continuation passing style vs monads

What are the differences between continuation passing style (cps) and monads.

like image 425
Carlo V. Dango Avatar asked Dec 24 '10 10:12

Carlo V. Dango


People also ask

What is continuation monad?

The Continuation monad represents computations in continuation-passing style (CPS). In continuation-passing style function result is not returned, but instead is passed to another function, received as a parameter (continuation).

What is a continuation in Haskell?

Continuations represent the future of a computation, as a function from an intermediate result to the final result ([1] section in Jeff Newbern's All About Monads)

What are the different classes of monads?

Common monadsNondeterminism using List monad to represent carrying multiple values. State using State monad. Read-only environment using Reader monad. I/O using IO monad.

What are monads used for?

A monad is an algebraic structure in category theory, and in Haskell it is used to describe computations as sequences of steps, and to handle side effects such as state and IO. Monads are abstract, and they have many useful concrete instances. Monads provide a way to structure a program.


3 Answers

As mentioned in The essence of functional programming:

Programming with monads strongly reminiscent of continuation—passing style (CPS), and this paper explores the relationship between the two. In a sense they are equivalent: CPS arises as a special case of a monad, and any monad may be embedded in CPS by changing the answer type. But the monadic approach provides additional insight and allows a finer degree of control.

That paper is quite rigorous, and actually it doesn't quite expand on the relationship between CPS and monads. Here I attempt to give an informal, but illustrative example:

(Note: Below is an understand of Monad from a newbie (myself), though after writing it it does appear to look like one of those high-rep users' answer. Please do take it with a ton of salt)

Consider the classic Maybe monad

-- I don't use the do notation to make it 
-- look similar to foo below

bar :: Maybe Int
bar =
    Just 5 >>= \x ->
    Just 4 >>= \y ->
    return $ x + y

bar' :: Maybe Int
bar' =
    Just 5 >>= \x ->
    Nothing >>= \_ ->
    return $ x

GHCi> bar
Just 9
GHCi> bar'
Nothing

So the computation stops as soon as Nothing is encountered, nothing new here. Let's try to mimic such a monadic behavior using CPS:

Here is our vanilla add function using CPS. We are using all Int here instead of algebric data type to make it easier:

add :: Int -> Int -> (Int -> Int) -> Int
add x y k = k (x+y)

GHCi> add 3 4 id
7

Notice how similar it is to a monad

foo :: Int
foo =
    add 1 2 $ \x -> -- 3
    add x 4 $ \y -> -- 7
    add y 5 $ \z -> -- 12
    z

GHCi> foo
12

OK. Suppose that we want the computation to be capped at 10. That is, whatever computation must stop when the next step would result in a value larger than 10. This is sort of like saying "a Maybe computation must stop and return Nothing as soon as any value in the chain is Nothing). Let's see how we can do it by writing a "CPS transformer"

cap10 :: (Int -> Int) -> (Int -> Int)
cap10 k = \x ->
    if x <= 10 
    then 
        let x' = k x in 
        if x' <= 10 then x' else x
    else x

foo' :: Int
foo' =
    add 1 2 $ cap10 $ \x -> -- 3
    add x 4 $ cap10 $ \y -> -- 7
    add y 5 $ cap10 $ \z -> -- 12
    undefined

GHCi> foo'
7

Notice that the final return value can be undefined, but that is fine, because the evaluation stops at the 3rd step (z).

We can see that cap10 "wraps" the normal continuation with some extra logic. And that's very close to what monad to -- glue computations together with some extra logic.

Let's go one step further:

(>>==) :: ((Int -> Int) -> Int) -> (Int -> Int) -> Int
m >>== k = m $ cap10 k

foo'' :: Int
foo'' =
    add 1 2 >>== \x -> -- 3
    add x 4 >>== \y -> -- 7
    add y 5 >>== \z -> -- 12
    undefined

GCHi> foo''
7

Woa! Maybe we have just invented the Cap10 monad!

Now if we look at the source code of Cont, we see that Cont is

newtype Cont r a = Cont { runCont :: (a -> r) -> r }

The type of runCont is

Cont r a -> (a -> r) -> r
((a -> r) -> r) -> (a -> r) -> r

Which lines up nicely with the type of our >>==

Now to actually answer the question

Now after typing all this I reread the original question. The OP asked for the "difference" :P

I guess the difference is CPS gives the caller more control, where as usually the >>= in a monad is fully controlled by the monad's author.

like image 119
kizzx2 Avatar answered Nov 07 '22 19:11

kizzx2


You might want to have a look at this http://blog.sigfpe.com/2008/12/mother-of-all-monads.html

like image 26
Peter Jankuliak Avatar answered Nov 07 '22 17:11

Peter Jankuliak


An interesting paper which explores the issue is "Imperative functional programming", by Peyton Jones and Wadler.

It's the paper which introduced monadic IO, and it has comparisons to alternative approaches, including CPS.

The authors conclude:

So monads are more powerful than continuations, but only because of the types! It is not clear whether this is only an artifact of the Hindley-Milner type system, or whether the types are revealing a difference of fundamental importance (our own intuition it's the latter -- but it's only an intuition.)

like image 27
Federico Squartini Avatar answered Nov 07 '22 17:11

Federico Squartini