As in my previous question, I'm trying to wrap the Data.Binary.Put monad into another monad so that later I can ask it questions like "how many bytes it's going to write" or "what is the current position in file".
Before, I thought that understanding why it leaks memory while using a trivial (IdentityT?) wrapper would lead me to solving my problem. But even though you guys have helped me resolve the problem with the trivial wrapper, wrapping it with something usefull like StateT or WriterT still consumes too much memory (and usually crashes).
For example, this is one way I'm trying to wrap it and which leaks memory for big input:
type Out = StateT Integer P.PutM ()
writeToFile :: String -> Out -> IO ()
writeToFile path out = BL.writeFile path $ P.runPut $ do runStateT out 0
return ()
Here is a more complete code sample that demonstrates the problem.
What I would like to know is this:
- What is happending inside the program that causes the memory leak?
- What can I do to fix it?
For my second question I think I should explain in more details what I intend the data to look on disk: It is basically a tree structure where each node of the tree is represented as an offset table to it's children (plus some additional data). So to calculate offset of n-th children into the offset table I need to know the sizes of children 0 to n-1 plus the current offset (to simplify things, let's say each node has fixed number of childs).
Thanks for looking.
UPDATE:
Thanks to nominolo I can now create a monad that wraps around the Data.Binary.Put, tracks current offset and uses almost no memory. This is done by dropping the use of StateT transformer in favor of a different state threading mechanism that uses Continuations.
Like this:
type Offset = Int
newtype MyPut a = MyPut
{ unS :: forall r . (Offset -> a -> P.PutM r) -> Offset -> P.PutM r }
instance Monad MyPut where
return a = MyPut $ f s -> f s a
ma >>= f = MyPut $ fb s -> unS ma (s' a -> unS (f a) fb s') s
writeToFile :: String -> MyPut () -> IO ()
writeToFile path put =
BL.writeFile path $ P.runPut $ peal put >> return ()
where peal myput = unS myput (o -> return) 0
getCurrentOffset :: MyPut Int
getCurrentOffset = MyPut $ f o -> f o o
lift' n ma = MyPut $ f s -> ma >>= f (s+n)
However I still have a problem with tracking how many bytes is MyPut going to write on disk. In particular, I need to have a function with signature like this:
getSize :: MyPut a -> MyPut Int
or
getSize :: MyPut a -> Int
My aproach was to wrap the MyPut monad inside WriterT transformer (something like this). But that started to consume too much memory again. As sclv mentions in comments under nominolos answer, WriterT somehow cancels out the effect of continuations. He also mentions that getting the size should be possible directly from the MyPut monad that I already have, but all my attempts to do so ended in non compilable code or an infinite loop :-|.
Could someone please help further?
See Question&Answers more detail:
os