Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why ContT doesn't deal with the inner monad?

The bind strategy for ContT ignores the inner monad, in fact the code is the same as for Cont.

Following the analogy from other Monad Transformers I would have implemented it this way:

return x = ContT ($ (return x))
(>>=) x f = ContT (\k -> runContT x ((=<<) (\a -> runContT (f a) k)))

Then for example this expression:

do 
    x1 <- ContT (\k -> k [1, 2])
    x2 <- ContT (\k -> k [10, 20])
    return ((+) x1 x2) 

Would result in [11, 21, 12, 22]

My question is what was the reason behind that design decision? Why was it implemented that way, which makes it very different from other Monad Transformers, note that the functor instance is:

fmap f m = ContT $ \c -> runContT m (c . f)

rather than:

fmap f m = ContT $ runCont $ (fmap . fmap) f (Cont (runContT m))

which is more or less the same as for other Monad Transformers, but the current implementation seems to break the functor composition, I mean since monads do not compose automatically we may have discussions about different bind strategies but for functors and applicatives it should always be the same and here it seems to be quite different. Was it a hack to get some code which represent more use cases working?

Even IdentityT works that way, but I read that the bind strategy for Cont is in fact the same as for Identity, so what about ContT and IdentityT?

like image 705
Gus Avatar asked Jan 20 '15 13:01

Gus


2 Answers

(>>=) doesn't need to deal with the inner monad (besides that it's not possible to implement it in a way you proposed, as chi showed), because we can just lift monadic values and get the desired semantics.

lift :: Monad m => m a -> ContT r m a
lift ma = ContT (ma >>=)

Or as it is in the standard library:

instance MonadTrans (ContT r) where
    lift m = ContT (m >>=)

Now we have

import Control.Monad.Trans.Cont
import Control.Monad.Trans.Class

test :: ContT r [] Int
test = do
  x <- lift [1, 2]
  y <- lift [10, 20]
  return (x + y)

-- evalContT test == [11, 21, 12, 22]

In other words, with the standard monad instance for ContT we can already manipulate the current continuations arbitrarily, so alternative implementations could hardly buy us anything.

like image 166
András Kovács Avatar answered Oct 08 '22 10:10

András Kovács


Let's check the types for the proposed implementation of (>>=):

> let foo x f = ContT (\k -> runContT x ((=<<) (\a -> runContT (f a) k)))
> :t foo
foo
  :: Monad m =>
     ContT r m (m a) -> (a -> ContT r m a1) -> ContT r m a1
     ^^^^^^^^^^^^^^^

That should be ContT r m a to match the type for (>>=).

Similarly for return:

> let bar  x = ContT ($ (return x))
> :t bar
bar :: Monad m1 => a -> ContT r m (m1 a)
                        ^^^^^^^^^^^^^^^^

There's an extra m1 above.

like image 45
chi Avatar answered Oct 08 '22 10:10

chi