Por que agrupar a mônada Data.Binary.Put cria um vazamento de memória? (Parte 2)

Como no meupergunta anterior, Estou tentando agrupar a mônada Data.Binary.Put em outra mônada para que mais tarde eu possa fazer perguntas como "quantos bytes ele escreverá" ou "qual é a posição atual no arquivo".

Antes, eu pensava que entender por que ele vaza memória ao usar um invólucro trivial (IdentityT?) Me levaria a resolver meu problema. Mas mesmo que vocês tenham me ajudado a resolver o problema com o invólucro trivial, envolvê-lo com algo útil como StateT ou WriterT ainda consome muita memória (e geralmente trava).

Por exemplo, esta é uma maneira que estou tentando envolvê-lo e que vaza memória para grandes entradas:

type Out = StateT Integer P.PutM ()

writeToFile :: String -> Out -> IO ()
writeToFile path out = BL.writeFile path $ P.runPut $ do runStateT out 0
                                                         return ()

Aqui é um exemplo de código mais completo que demonstra o problema.

O que eu gostaria de saber é o seguinte:

O que está acontecendo dentro do programa que causa o vazamento de memória?O que posso fazer para corrigi-lo?

Para minha segunda pergunta, acho que devo explicar com mais detalhes o que pretendo que os dados visualizem no disco: é basicamente uma estrutura em árvore onde cada nó da árvore é representado como uma tabela de deslocamento para seus filhos (além de alguns dados adicionais). Portanto, para calcular o deslocamento de n-ésimo filho na tabela de deslocamento, preciso conhecer o tamanho dos filhos de 0 a n-1 mais o deslocamento atual (para simplificar, digamos que cada nó tenha um número fixo de filhos).

Obrigado por procurar.

ATUALIZAÇÃO: Graças ao nominolo, agora posso criar uma mônada que envolve o Data.Binary.Put, rastreia o deslocamento atual e quase não usa memória. Isso é feito descartando o uso do transformador StateT em favor de um mecanismo de encadeamento de estado diferente que usa Continuations.

Como isso:

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)

No entanto, ainda tenho um problema ao rastrear quantos bytes o MyPut vai gravar no disco. Em particular, preciso ter uma função com assinatura como esta:

getSize :: MyPut a -> MyPut Int
ou
getSize :: MyPut a -> Int

Minha abordagem foi envolver a mônada MyPut dentro do transformador WriterT (algo comoesta) Mas isso começou a consumir muita memória novamente. Como o sclv menciona nos comentários sob a resposta de nominolos, o WriterT de alguma forma cancela o efeito das continuações. Ele também menciona que a obtenção do tamanho deve ser possível diretamente da mônada MyPut que eu já tenho, mas todas as minhas tentativas para fazê-lo terminaram em código não compilável ou em um loop infinito: - |.

Alguém poderia ajudar mais?

questionAnswers(2)

yourAnswerToTheQuestion