读取任意数量的二进制消息

时间:2011-05-20 14:32:28

标签: haskell binary monads

我正在使用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进行排序。有什么建议吗?

谢谢!

2 个答案:

答案 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')