我正在使用Binary.Get从文件中解析二进制数据,并具有以下内容:
data FileMessageHeaders = FileMessageHeaders [FileMessageHeader]
data FileMessageHeader = FileMessageHeader ...
instance Binary FileMessageHeaders where
put = undefined
get = do
messages <- untilM get isEmpty
return (FileMessageHeaders messages)
instance Binary FileMessageHeader where
put = undefined
get = ..
我遇到的问题是来自hackage上的monad-loops的untilM使用了序列,所以我相信这是导致返回FileMessageHeader列表的头部的大量延迟,因为必须读取整个文件(这是正确?)。我无法想出一种方法来重写它并避免对文件中的所有FileMessageHeaders进行排序。有什么建议吗?
谢谢!
答案 0 :(得分:1)
正如FUZxxl所说,问题是untilM
; Get
monad是严格的,要求整个untilM
操作在返回之前完成。 IO与它无关。
最简单的事情可能是切换到attoparsec并将其用于解析而不是二进制。 Attoparsec支持流式解析,在这种情况下可能更容易使用。
如果无法切换到attoparsec,则需要使用二进制的一些低级函数,而不是仅使用Binary
实例。如下所示(完全未经测试)。
getHeaders :: ByteString -> [FileMessageHeader]
getHeaders b = go b 0
where
go bs n
| B.null bs = []
| otherwise = let (header, bs', n') = runGetState get bs n
in header : go bs' n'
不幸的是,这意味着您将无法使用Binary
实例或get
功能,您必须使用getHeaders
。它会传播。
答案 1 :(得分:0)
这里的问题是,在控制流可以继续之前必须完成IO
动作。因此,程序必须在评估之前读入所有消息。您可以尝试定义一个自己的组合子sequenceI
,它使用System.IO.Unsafe
中的unsafeInterleaveIO
函数。此功能允许您交错操作。例如,getContents
使用它。我会像这样定义sequenceI
:
sequenceI (x:xs) = do v <- x
vs <- unsafeInterleaveIO $ sequenceI xs
return (v:vs)
在这个组合器的顶部,您可以定义自己的untilM
,即流。这样做是留给读者的练习。
这是一个概念验证,未经测试的untilM实现:
untilMI f p = do
f' <- f
p' <- p
if p'
then return [f']
else do g' <- unsafeInterleaveIO $ untilMI f p
return (f' : g')