Как и в моем предыдущем вопросе , я пытаюсь заключить монаду Data.Binary.Put в другую монаду, чтобы позже я мог задать ей такие вопросы, как «сколько байт она собирается записать» или msgstr "какая текущая позиция в файле".
Раньше я думал, что понимание того, почему происходит утечка памяти при использовании тривиальной оболочки (IdentityT?), Приведет меня к решению моей проблемы. Но даже при том, что вы, ребята, помогли мне решить проблему с помощью тривиальной оболочки, обертывание ее чем-нибудь полезным, например, StateT или WriterT, все равно потребляет слишком много памяти (и обычно вылетает).
Например, это один из способов, который я пытаюсь обернуть, и который приводит к утечке памяти при большом вводе:
type Out = StateT Integer P.PutM ()
writeToFile :: String -> Out -> IO ()
writeToFile path out = BL.writeFile path $ P.runPut $ do runStateT out 0
return ()
Здесь - более полный пример кода, демонстрирующий проблему.
То, что я хотел бы знать, это:
- Что происходит внутри программы, что вызывает утечку памяти?
- Что я могу сделать, чтобы это исправить?
Что касается моего второго вопроса, я думаю, что должен объяснить более подробно, что я намерен просматривать данные на диске: это в основном древовидная структура, где каждый узел дерева представлен в виде таблицы смещений для его дочерних элементов (плюс некоторые дополнительные данные). Таким образом, чтобы вычислить смещение n-ых дочерних элементов в таблицу смещений, мне нужно знать размеры дочерних элементов от 0 до n-1 плюс текущее смещение (скажем, для упрощения, скажем, каждый узел имеет фиксированное число дочерних элементов).
Спасибо за внимание.
UPDATE:
Благодаря nominolo я теперь могу создать монаду, которая оборачивается вокруг Data.Binary.Put, отслеживает текущее смещение и почти не использует память. Это делается путем отказа от использования преобразователя StateT в пользу другого механизма потоков состояния, который использует Continuations.
Вот так:
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)
Однако у меня все еще есть проблема с отслеживанием количества байтов, которые MyPut собирается записать на диск. В частности, мне нужно иметь функцию с такой подписью:
getSize :: MyPut a -> MyPut Int
или
getSize :: MyPut a -> Int
Мой подход заключался в том, чтобы обернуть монаду MyPut внутри преобразователя WriterT (что-то вроде this ). Но это снова стало занимать слишком много памяти. Как sclv упоминает в комментариях под nominolos ответом, WriterT каким-то образом нейтрализует эффект продолжений. Он также упоминает, что получение размера должно быть возможным непосредственно из монады MyPut, которая у меня уже есть, но все мои попытки сделать это закончились некомпилируемым кодом или бесконечным циклом: - |.
Может кто-нибудь помочь, пожалуйста?