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.
lifttakes an untransformed monadic action and wraps it so that you can run it inside a transformer:In this case, the transformer is
ConduitM [Char] o, which is defined inData.Conduit.Internalas:Which derives its
MonadTransinstance usingGeneralizedNewtypeDerivingfrom the instance forPipe:Here’s a simpler example:
We are in
ReaderT Int (State Int)and ourmodifyaction is inState Int, so we need toliftthe 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 oflifted actions under onelift: