正如我之前的问题一样,我试图将 Data.Binary.Put monad 包装到另一个 monad 中,以便稍后我可以问它诸如“它将写入多少字节”或“文件中的当前位置是什么”之类的问题.
以前,我认为理解为什么它在使用琐碎的(IdentityT?)包装器时会泄漏内存将引导我解决我的问题。但是,即使你们帮助我解决了琐碎包装器的问题,用像 StateT 或 WriterT 这样有用的东西来包装它仍然会消耗太多内存(并且通常会崩溃)。
例如,这是我试图包装它的一种方式,它会泄漏大量输入的内存:
输入输出 = StateT 整数 P.PutM () writeToFile::String -> Out -> IO() writeToFile 路径输出 = BL.writeFile 路径 $P.runPut $do runStateT out 0 返回 ()
这是演示问题的更完整的代码示例。
我想知道的是:
- 导致内存泄漏的程序内部发生了什么?
- 我能做些什么来修复它?
对于我的第二个问题,我想我应该更详细地解释我希望数据在磁盘上看到的内容:它基本上是一个树结构,其中树的每个节点都表示为其子节点的偏移表(加上一些额外的数据)。因此,要计算第 n 个子节点的偏移量到偏移表中,我需要知道子节点 0 到 n-1 的大小加上当前偏移量(为简化起见,假设每个节点都有固定数量的子节点)。
感谢您的关注。
更新:感谢 nominolo,我现在可以创建一个环绕 Data.Binary.Put 的 monad,跟踪当前偏移量并且几乎不使用内存。这是通过放弃使用 StateT 转换器来支持使用 Continuations 的不同状态线程机制来完成的。
像这样:
类型偏移 = Int 新类型 MyPut a = MyPut { unS :: forall r 。(偏移量 -> a -> P.PutM r) -> 偏移量 -> P.PutM r } 实例 Monad MyPut 在哪里 返回 a = MyPut $ \fs -> fsa ma >>= f = MyPut $ \fb s -> unS ma (\s' a -> unS (fa) fb s') s writeToFile::String -> MyPut() -> IO() writeToFile 路径 put = BL.writeFile 路径 $P.runPut $peal put >> return() 其中peal myput = unS myput (\o -> return) 0 getCurrentOffset :: MyPut Int getCurrentOffset = MyPut $ \fo -> foo 提升' n ma = MyPut $ \fs -> ma >>= f (s+n)
但是,我仍然无法跟踪 MyPut 将在磁盘上写入多少字节。特别是,我需要一个带有这样签名的函数:
getSize :: MyPut a -> MyPut Int或者
getSize :: MyPut a -> Int
我的方法是将 MyPut monad 包装在 WriterT 转换器中(类似这样的东西)。但这又开始消耗过多的内存。正如 sclv 在 nominolos 答案下的评论中提到的那样,WriterT 以某种方式抵消了延续的影响。他还提到应该可以直接从我已经拥有的 MyPut monad 中获取大小,但是我所有这样做的尝试都以不可编译的代码或无限循环结束:-|。
有人可以进一步帮助吗?