-
Notifications
You must be signed in to change notification settings - Fork 195
Dealing with monad transformers
NOTE: Since conduit 1.0.11, the monad transformer situation is much improved due to the Data.Conduit.Lift module. For more information, see the announcement blog post.
conduit provides the transPipe
function in order to allow pipes with different underlying monads to interact. However, some of the semantics of this function are surprising. In particular, it will run the monad modification function multiple times. This means that if you use transPipe
to unwrap a monad, side effects will be thrown away. Perhaps an example will demonstrate best:
{-# LANGUAGE OverloadedStrings #-}
import Data.Conduit
import qualified Data.Conduit.List as CL
import Control.Monad.State
import Control.Monad.Trans.Class
source :: Source IO ()
source = CL.sourceList $ replicate 10 ()
replaceNum :: Conduit () (StateT Int IO) Int
replaceNum = awaitForever $ \() -> do
i <- lift get
lift $ put $ i + 1
yield i
main :: IO ()
main = do
x <- source $$ transPipe (flip evalStateT 1) replaceNum =$ CL.consume
print x
y <- flip evalStateT 1
$ transPipe lift source $$ replaceNum =$ CL.consume
print y
The output from this program is:
[1,1,1,1,1,1,1,1,1,1]
[1,2,3,4,5,6,7,8,9,10]
What's happening in the first case is that evalStateT
is run anew for each time the Pipe
is called, and the results are then thrown away. Therefore, each time the pipe is run, it is run with a state of 1.
In the second case, we only run the state once, and therefore the state is properly threaded and side effects retained between runs. To get the types to work, we lift the monad in the source instead. Since the lift
call does not discard side effects, all side effects are retained.
In other words:
- You can always use
transPipe
with functions which do not discard data. Examples arelift
andrunReaderT
. - It's best to lift functions to higher monads than to constantly unwrap.
- If you must unwrap and need to retain side effects, you can try such tricks as putting a mutable variable in a
ReaderT
instead of usingStateT
.