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
?
(>>=)
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With