Чтение в произвольном количестве двоичных сообщений - PullRequest
1 голос
/ 20 мая 2011

Я анализирую двоичные данные из файлов, используя 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 = ..

Проблема, с которой я сталкиваюсь, состоит в том, что tillM из циклов монадла на хакерской основе использует последовательность, поэтому яПолагайте, что именно это вызывает огромную задержку в возврате заголовка списка FileMessageHeader, поскольку весь файл должен быть прочитан (это правильно?).У меня возникли проблемы с придуманием способа переписать это и избежать последовательности всех FileMessageHeaders в файле.Есть предложения?

Спасибо!

Ответы [ 2 ]

1 голос
/ 21 мая 2011

Как отмечает FUZxxl, проблема в untilM; монада Get является строгой и требует, чтобы все действие 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. Это будет течь, хотя.

0 голосов
/ 20 мая 2011

Проблема здесь в том, что действие IO должно завершиться, прежде чем поток управления сможет продолжиться. Таким образом, программа должна прочитать все сообщения, прежде чем они будут оценены. Вы можете попытаться определить собственный комбинатор sequenceI, который использует функцию unsafeInterleaveIO из System.IO.Unsafe. Эта функция позволяет вам чередовать действия. Используется, например, getContents. Я бы определил sequenceI так:

sequenceI (x:xs) = do v <- x
                      vs <- unsafeInterleaveIO $ sequenceI xs
                      return (v:vs)

Сверху этого комбинатора вы можете определить свои untilM, которые будут потоковыми. Это оставлено читателю в качестве упражнения.

Редактировать (исправлено для компиляции)

Это проверка концепции, не проверенная реализация tillM:

untilMI f p = do
  f' <- f
  p' <- p
  if p'
    then return [f']
    else do g' <- unsafeInterleaveIO $ untilMI f p
            return (f' : g')
...