我正在尝试将Data.Binary.Put monad包装到另一个monad中,以便稍后我可以问一些问题,例如“它要写多少字节”或“文件中的当前位置是什么”。但即便是非常微不足道的包装:
data Writer1M a = Writer1M { write :: P.PutM a }
or
data Writer2M a = Writer2M { write :: (a, P.Put) }
造成巨大的空间泄漏,程序通常崩溃(占用4GB内存后)。这是我到目前为止所尝试的:
-- 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的新手,这对我没有任何意义,但是封装monad看起来非常微不足道所以我猜我有一些明显的东西。
感谢您的光临。
更新: 以下是演示此问题的示例代码:http://hpaste.org/43400/why_wrapping_the_databinaryp
UPDATE2: 这个问题还有第二部分here。
答案 0 :(得分:4)
经过一番探索后,我发现问题似乎是使用二进制(>> =)来实现(>>)。以下对Writer1M monad实现的补充解决了这个问题:
m >> k = Writer1M $ write m >> write k
虽然这个版本仍会泄漏内存:
m >> k = Writer1M $ write m >>= const (write k)
查看binary's source,(>>)似乎明确地丢弃了第一个monad的结果。但不确定这究竟是如何防止泄漏的。我最好的理论是GHC否则会保留在PairS对象上,并且“a”引用会泄漏,因为它永远不会被查看。
答案 1 :(得分:2)
你有没有试过让monad更严格?例如。尝试使您的datatyp的构造函数严格/用newtype替换它们。
我不知道这里的确切问题是什么,但这是泄漏的常见原因。
PS:并尝试删除不必要的lambdas,例如:
ma >>= f = Writer1M $ (write ma) >=> write . f