Почему обертывание Data.Binary.Put monad создает утечку памяти?
Я пытаюсь обернуть монаду Data.Binary.Put в другую, чтобы позже я мог задать ему вопросы типа "сколько байтов он пишет" или "что такое текущая позиция в файле". Но даже очень тривиальные обертывания вроде:
data Writer1M a = Writer1M { write :: P.PutM a }
or
data Writer2M a = Writer2M { write :: (a, P.Put) }
создает огромную утечку пространства, и программа обычно вылетает (после захвата 4 ГБ ОЗУ). Вот что я пробовал до сих пор:
-- This works well and consumes almost no memory.
type Writer = P.Put
writer :: P.Put -> Writer
writer put = put
writeToFile :: String -> Writer -> IO ()
writeToFile path writer = BL.writeFile path (P.runPut writer)
-- This one will cause memory leak.
data Writer1M a = Writer1M { write :: P.PutM a }
instance Monad Writer1M where
return a = Writer1M $ return a
ma >>= f = Writer1M $ (write ma) >>= \a -> write $ f a
type WriterM = Writer1M
type Writer = WriterM ()
writer :: P.Put -> Writer
writer put = Writer1M $ put
writeToFile :: String -> Writer -> IO ()
writeToFile path writer = BL.writeFile path (P.runPut $ write writer)
-- This one will crash as well with exactly the
-- same memory foot print as Writer1M
data Writer2M a = Writer2M { write :: (a, P.Put) }
instance Monad Writer2M where
return a = Writer2M $ (a, return ())
ma >>= f = Writer2M $ (b, p >> p')
where (a,p) = write ma
(b,p') = write $ f a
type WriterM = Writer2M
type Writer = WriterM ()
writer :: P.Put -> Writer
writer put = Writer2M $ ((), put)
writeToFile :: String -> Writer -> IO ()
writeToFile path writer = BL.writeFile path (P.runPut $ snd $ write writer)
Я новичок в Haskell, и это не имеет для меня никакого смысла, но монады-обложки кажутся очень тривиальными, поэтому я предполагаю, что есть что-то очевидное, чего я не хватает.
Спасибо, что посмотрели.
UPDATE:
Вот пример кода, который демонстрирует проблему: http://hpaste.org/43400/why_wrapping_the_databinaryp
UPDATE2:
Здесь также есть вторая часть этого вопроса .
Ответы
Ответ 1
После некоторого разговора, я обнаружил, что проблема заключается в использовании бинарного ( → =) для реализации ( → ). Следующее дополнение к реализации монады Writer1M решает проблему:
m >> k = Writer1M $ write m >> write k
В то время как эта версия все еще теряет память:
m >> k = Writer1M $ write m >>= const (write k)
Глядя на двоичный источник, ( → ), кажется, явно отказывается от результата первой монады. Не уверен, как именно это предотвращает утечку. Моя лучшая теория заключается в том, что GHC иначе держится на объекте PairS, а ссылка "a" протекает, потому что на нее никогда не смотрят.
Ответ 2
Вы пытались сделать монаду более строгой? Например. попробуйте сделать конструкторы вашего типа данных строгими/замените их на новый тип.
Я не знаю, какая именно проблема здесь, но это обычный источник утечек.
PS: И попробуйте удалить ненужные лямбды, например:
ma >>= f = Writer1M $ (write ma) >=> write . f