It has already been discussed that mapM
is inherently not lazy, e.g. here and here. Now I'm struggling with a variation of this problem where the mapM
in question is deep inside a monad transformer stack.
Here's a function taken from a concrete, working (but space-leaking) example using LevelDB that I put on gist.github.com:
-- read keys [1..n] from db at DirName and check that the values are correct
doRead :: FilePath -> Int -> IO ()
doRead dirName n = do
success <- runResourceT $ do
db <- open dirName defaultOptions{ cacheSize= 2048 }
let check' = check db def in -- is an Int -> ResourceT IO Bool
and <$> mapM check' [1..n] -- space leak !!!
putStrLn $ if success then "OK" else "Fail"
This function reads the values corresponding to keys [1..n]
and checks that they are all correct. The troublesome line inside the ResourceT IO a
monad is
and <$> mapM check' [1..n]
One solution would be to use streaming libraries such as pipes
, conduit
, etc. But these seem rather heavy and I'm not at all sure how to use them in this situation.
Another path I looked into is ListT
as suggested here. But the type signatures of ListT.fromFoldable :: [Bool]->ListT Bool
and ListT.fold :: (r -> a -> m r) -> r -> t m a -> mr
(where m
=IO
and a
,r
=Bool
) do not match the problem at hand.
What is a 'nice' way to get rid of the space leak?
Update: Note that this problem has nothing to do with monad transformer stacks! Here's a summary of the proposed solutions:
1) Using Streaming
:
import Streaming
import qualified Streaming.Prelude as S
S.all_ id (S.mapM check' (S.each [1..n]))
2) Using Control.Monad.foldM
:
foldM (\a i-> do {b<-check' i; return $! a && b}) True [1..n]
3) Using Control.Monad.Loops.allM
allM check' [1..n]