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.
lift
takes 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.Internal
as:Which derives its
MonadTrans
instance usingGeneralizedNewtypeDeriving
from the instance forPipe
:Here’s a simpler example:
We are in
ReaderT Int (State Int)
and ourmodify
action is inState Int
, so we need tolift
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 oflift
ed actions under onelift
: