Explanation for need of lifting when using ResourceT

150 views Asked by At

I'm trying to figure out how Conduits work and am getting caught up in the monads and transformers involved.

I boiled some sample code down to the following, which works:

import Control.Monad.Trans.Class (lift)
import Data.Conduit
import Data.Conduit.Binary (sinkFile)
import Data.Conduit.List
import Network.HTTP.Conduit

downloadContent manager = do
  mx <- await
  case mx of
    Nothing   -> return ()
    Just name -> do
      req <- lift $ parseUrl $ "http://" ++ name ++ ".com/"
      res <- lift $ http req manager
      lift $ responseBody res $$+- sinkFile $ name ++ ".html"
      downloadContent manager

main = do
  runResourceT $ do
    withManager $ \manager -> do
      sourceList ["google", "yahoo"] $$ downloadContent manager

What I don't understand is why I need to lift inside downloadContent. Why do I need to use lift in the above code? What am I lifting from and to? If I look at signatures:

parseUrl
  :: failure-0.2.0.1:Control.Failure.Failure HttpException m =>
     String -> m (Request m')

http
  :: (MonadResource m, MonadBaseControl IO m) =>
     Request m
     -> Manager
     -> m (Response
             (ResumableSource m Data.ByteString.Internal.ByteString))

($$+-) :: Monad m => ResumableSource m a -> Sink a m b -> m b

downloadContent
  :: (MonadResource m, MonadBaseControl IO m,
      failure-0.2.0.1:Control.Failure.Failure HttpException m) =>
     Manager -> ConduitM [Char] o m ()

class (MonadThrow m, MonadUnsafeIO m, MonadIO m, Applicative m) => MonadResource m

This is not really helping me to understand what is happening.

1

There are 1 answers

0
Jon Purdy On

lift takes an untransformed monadic action and wraps it so that you can run it inside a transformer:

lift :: (MonadTrans t, Monad m) => m a -> t m a

In this case, the transformer is ConduitM [Char] o, which is defined in Data.Conduit.Internal as:

newtype ConduitM i o m r = ConduitM { unConduitM :: Pipe i i o () m r }
    deriving (..., MonadTrans, ...)

Which derives its MonadTrans instance using GeneralizedNewtypeDeriving from the instance for Pipe:

instance MonadTrans (Pipe l i o u) where
    lift mr = PipeM (Done `liftM` mr)

Here’s a simpler example:

action :: ReaderT Int (State Int) ()
action = do           -- In the 'ReaderT Int (State Int)' monad.
  x <- ask            -- Ask for the (outer) 'Reader' environment.
  lift $ do           -- In the 'State Int' monad.
    modify (+x)       -- Modify the (inner) 'State' a couple of times.
    modify (+x)

main = print $ execState (runReaderT action 1) 1

We are in ReaderT Int (State Int) and our modify action is in State Int, so we need to lift the action in order to run it in the transformer. Note that, like in the above example, you ought to be able to merge your series of lifted actions under one lift:

Just name -> do
  lift $ do
    req <- parseUrl $ "http://" ++ name ++ ".com/"
    res <- http req manager
    responseBody res $$+- sinkFile $ name ++ ".html"
  downloadContent manager