MonadCont under the hood
From HaskellWiki
m |
m (→Return) |
||
| (8 intermediate revisions not shown.) | |||
| Line 1: | Line 1: | ||
| - | This tutorial is a response to the following [http://stackoverflow.com/questions/3322540/tutorial-to-disassemble-the-haskell-cont-monad | + | This tutorial is a response to the following [http://stackoverflow.com/questions/3322540/tutorial-to-disassemble-the-haskell-cont-monad Stack Overflow question]. There's a short but useful description of <tt>Cont</tt> and <tt>MonadCont</tt> operations in the [http://hackage.haskell.org/packages/archive/mtl/1.1.0.2/doc/html/Control-Monad-Cont.html Control.Monad.Cont documentation], but it doesn't really describe how the continuation monad does its thing. This is an attempt at much more detailed explanation of what Cont and MonadCont are doing under the hood. |
This tutorial assumes a working knowledge of Haskell, though of course it doesn't assume that you understood the implementation of <hask>Control.Monad.Cont</hask> the first time you read it! | This tutorial assumes a working knowledge of Haskell, though of course it doesn't assume that you understood the implementation of <hask>Control.Monad.Cont</hask> the first time you read it! | ||
| Line 16: | Line 16: | ||
=Sequencing Continuation-Style Computations= | =Sequencing Continuation-Style Computations= | ||
| - | == | + | ==Basic Sequencing== |
| - | <hask>Cont</hask> objects can be chained together, so that the continuation you pass in threads through the guts of all the <hask>Cont</hask> objects in the chain before it's finally invoked. The way they chain is the way <hask>Cont</hask> works: each object in the chain invokes a continuation that ''has the next object's computation prepended to the final continuation''. Let's say we have a chain of <hask>Cont</hask> objects <hask> | + | <hask>Cont</hask> objects can be chained together, so that the continuation you pass in threads through the guts of all the <hask>Cont</hask> objects in the chain before it's finally invoked. The way they chain is the way <hask>Cont</hask> works: each object in the chain invokes a continuation that ''has the next object's computation prepended to the final continuation''. Let's say we have a chain of <hask>Cont</hask> objects <hask>f1 -> f2 -> f3</hask>, and let's say you had a continuation <hask>c3</hask> that you want to pass to the chain. Then: |
| - | * <hask> | + | * <hask>f3</hask> needs to invoke <hask>c3</hask> when it's done. |
| - | * <hask> | + | * <hask>f2</hask> needs to invoke a continuation <hask>c2</hask> that will invoke <hask>f3</hask>, which will invoke <hask>c3</hask>. |
| - | * <hask> | + | * <hask>f1</hask> needs to invoke a continuation <hask>c1</hask>,<br/> which will invoke <hask>f2</hask>,<br/> which will invoke <hask>c2</hask>,<br/> which will invoke <hask>f3</hask>,<br/> which will finally invoke <hask>c3</hask>. |
| - | + | To chain the <hask>Cont</hask> objects together, then, we need to create the appropriate continuation functions <hask>c1</hask> and <hask>c2</hask> and make sure they get passed as the continuation argument to <hask>f1</hask> and <hask>f2</hask> respectively. | |
==Extending to Monad== | ==Extending to Monad== | ||
| - | + | Extending this idea to the <hask>Monad</hask> class in general, there's an extra wrinkle: we allow for the value of one computation to affect ''which'' <hask>Cont</hask> object gets invoked next. In this world: | |
* <hask>return</hask> takes a value and produces a <hask>Cont</hask> object that just passes that value to its continuation. | * <hask>return</hask> takes a value and produces a <hask>Cont</hask> object that just passes that value to its continuation. | ||
| - | * <hask> | + | * The bind operator <hask>(>>=)</hask> takes a <hask>Cont</hask> object, and a ''function that produces another <hask>Cont</hask> object given a value from the first'', and chains them together into one <hask>Cont</hask> object. That object, when invoked, is going to: |
** take a single continuation object <hask>C</hask>, | ** take a single continuation object <hask>C</hask>, | ||
** produce an intermediate value, | ** produce an intermediate value, | ||
| Line 52: | Line 52: | ||
Thus, <hask>return</hask> in the <hask>Cont</hask> monad passes the result of its computation directly to the continuation it's given. | Thus, <hask>return</hask> in the <hask>Cont</hask> monad passes the result of its computation directly to the continuation it's given. | ||
| + | |||
| + | A mathematical analogon that may help is the "insert a" functional: it takes a function f, and the result is the value f(a) of that function at x=a. Call it "insert(a)", then | ||
| + | <haskell> | ||
| + | insert(a) <---> return a | ||
| + | insert(a)(f(x) = x^2) <---> runCont (return a) (\x -> x^2) | ||
| + | == f(a) == (\x -> x^2) a | ||
| + | == a^2 == a^2 | ||
| + | </haskell> | ||
==Bind== | ==Bind== | ||
| Line 63: | Line 71: | ||
<haskell> | <haskell> | ||
| - | + | m >>= k = let s c = runCont m c | |
| - | + | t c = \a -> runCont (k a) c | |
| - | + | in Cont $ \c -> s (t c) | |
</haskell> | </haskell> | ||
| Line 71: | Line 79: | ||
=Exploring the Monad= | =Exploring the Monad= | ||
| - | Here's a simple example | + | Here's a simple example that should help illustrate the monad in action: |
<haskell> | <haskell> | ||
| Line 123: | Line 131: | ||
<hask>h</hask> is a function that takes a value and produces a <hask>Cont</hask> object depending on the value it's given. | <hask>h</hask> is a function that takes a value and produces a <hask>Cont</hask> object depending on the value it's given. | ||
| - | ''Lemma:'' <hask>runCont Cont $</hask> effectively | + | ''Lemma:'' The sequence of terms <hask>runCont Cont $</hask> effectively cancel out, i.e. <hask>runCont (Cont $ \c -> ...)</hask> is simply the function <hask>\c -> ...</hask>. This is because <hask>runCont</hask> is a field selector of <hask>Cont</hask> objects, and <hask>Cont</hask> objects only have that one field. |
Therefore, <hask>(return 5) >>= h</hask> expands and simplifies to: | Therefore, <hask>(return 5) >>= h</hask> expands and simplifies to: | ||
<haskell> | <haskell> | ||
| - | let s c = c 5 | + | doC = let s c = c 5 |
| - | + | t c = \a -> runCont (h a) c | |
| - | + | in Cont $ \c -> s (t c) | |
</haskell> | </haskell> | ||
| Line 146: | Line 154: | ||
=> finalC (5*3) -- apply \c... to finalC | => finalC (5*3) -- apply \c... to finalC | ||
=> "Done: 15" -- apply *; apply finalC to final value! | => "Done: 15" -- apply *; apply finalC to final value! | ||
| + | |||
| + | If you changed doC to <hask>return 4 >>= h</hask>, the derivation would be almost identical to the above, except that 4 would pass through to h, which would unfold to g instead. "Done: 2" should be the result. | ||
=MonadCont and callCC= | =MonadCont and callCC= | ||
| - | One final extension to this which | + | One final extension to this monad, which can be extremely useful in practice, is the <hask>MonadCont</hask> class, which provides a <hask>callCC</hask> operation. <hask>callCC</hask> creates a <hask>Cont</hask> object that invokes a function to construct a <hask>Cont</hask> object, and then runs it with the continuation it's given. However, it provides that function an ''alternate'' continuation that can be invoked to "break out" of the computation and simply pass a value to the continuation that was active when <hask>callCC</hask> was invoked. This function's operation is definitely easier to understand by seeing it in action. Evaluate the following code, replacing the corresponding functions above: |
<haskell> | <haskell> | ||
| Line 187: | Line 197: | ||
</haskell> | </haskell> | ||
| - | The key is <hask>backtrack</hask>, which takes whatever "inner" continuation is active when backtrack is invoked, completely ignores it, and simply passes its value to the "outer" continuation <hask>c</hask>. (Compare this to the definition of <hask>return</hask>, which always uses the continuation it's given.) <hask>f</hask>is the function passed to <hask>callCC</hask>, whose extent provides the context under which <hask>backtrack</hask> can be used. | + | The key is <hask>backtrack</hask>, which takes whatever "inner" continuation is active when backtrack is invoked, completely ignores it, and simply passes its value to the "outer" continuation <hask>c</hask>. (Compare this to the definition of <hask>return</hask>, which always uses the continuation it's given.) <hask>f</hask> is the function passed to <hask>callCC</hask>, whose extent provides the context under which <hask>backtrack</hask> can be used. |
[[Category:Monad]] | [[Category:Monad]] | ||
[[Category:Tutorials]] | [[Category:Tutorials]] | ||
Current revision
This tutorial is a response to the following Stack Overflow question. There's a short but useful description of Cont and MonadCont operations in the Control.Monad.Cont documentation, but it doesn't really describe how the continuation monad does its thing. This is an attempt at much more detailed explanation of what Cont and MonadCont are doing under the hood.
This tutorial assumes a working knowledge of Haskell, though of course it doesn't assume that you understood the implementation ofContents |
1 Introducing Continuations and the Cont type
Continuations are functions that represent "the remaining computation to do." Their representation here is- takes a continuation as an argument
- does whatever it needs to do
- produces a value of type at the end, presumably by invoking the continuation.r
2 Sequencing Continuation-Style Computations
2.1 Basic Sequencing
- needs to invokef3when it's done.c3
- needs to invoke a continuationf2that will invokec2, which will invokef3.c3
- needs to invoke a continuationf1,c1
which will invoke,f2
which will invoke,c2
which will invoke,f3
which will finally invoke.c3
2.2 Extending to Monad
Extending this idea to the- takes a value and produces areturnobject that just passes that value to its continuation.Cont
- The bind operator takes a(>>=)object, and a function that produces anotherContobject given a value from the first, and chains them together into oneContobject. That object, when invoked, is going to:Cont
- take a single continuation object ,C
- produce an intermediate value,
- use that intermediate value to select/create the next object to invoke,Cont
- invoke that object withContC
- take a single continuation object
3 Understanding the Monad
3.1 Return
The code:
return a = Cont ($ a)
is equivalent to the following code:
return a = Cont $ \c -> c a
A mathematical analogon that may help is the "insert a" functional: it takes a function f, and the result is the value f(a) of that function at x=a. Call it "insert(a)", then
insert(a) <---> return a insert(a)(f(x) = x^2) <---> runCont (return a) (\x -> x^2) == f(a) == (\x -> x^2) a == a^2 == a^2
3.2 Bind
The code:
m >>= k = Cont $ \c -> runCont m $ \a -> runCont (k a) c
is a terse way of saying the following:
m >>= k = let s c = runCont m c t c = \a -> runCont (k a) c in Cont $ \c -> s (t c)
4 Exploring the Monad
Here's a simple example that should help illustrate the monad in action:
f :: Int -> Cont r Int f x = Cont $ \c -> c (x * 3) g :: Int -> Cont r Int g x = Cont $ \c -> c (x - 2)
BTW, they can be equivalently written as:
f x = return (x * 3) g x = return (x - 2)
where they look very similar to normal functions. I'm writing them longhand to show you explicitly what the functions are doing.
h :: Int -> Cont r Int h x | x == 5 = f x | otherwise = g x
doC :: Cont r Int doC = return 5 >>= h
And we'll invoke it like this:
finalC :: Show a => a -> String finalC x = "Done: " ++ show(x) runCont doC finalC
Let's see if you're right:
doC = let s c = c 5 t c = \a -> runCont (h a) c in Cont $ \c -> s (t c)
runCont doC finalC => runCont (Cont $ \c -> s (t c)) finalC -- unfold doC => s (t finalC) -- simplify with lemma and apply to finalC => (t finalC) 5 -- unfold s => (\a -> runCont (h a) finalC) 5 -- unfold t => runCont (h 5) finalC -- apply \a... to 5 => runCont (f 5) finalC -- unfold h => runCont (Cont $ \c -> c (5*3)) finalC -- unfold f => (\c -> c (5*3)) finalC -- simplify with lemma => finalC (5*3) -- apply \c... to finalC => "Done: 15" -- apply *; apply finalC to final value!If you changed doC to
5 MonadCont and callCC
One final extension to this monad, which can be extremely useful in practice, is theh :: Int -> (Int -> Cont r Int) -> Cont r Int h x abort | x == 5 = f x | otherwise = abort (-1) doC n = return n >>= \x -> callCC (\abort -> h x abort) >>= \y -> g y
doC n = return n >>= \x -> callCC (\abort -> h x abort >>= \y -> g y)
callCC f = Cont $ \c -> runCont (f (\a -> Cont $ \_ -> c a )) c
can be written as
callCC f = let backtrack a = Cont $ \_ -> c a in Cont $ \c -> runCont (f backtrack) c
