Как читать разные куски из канала (например, строку до LF, а затем 10 байт)?

Для одного сетевого протокола мне нужно иметь возможность гибко считывать различные типы фрагментов из файла Source m ByteString. Есть комбинатор lines, который разбивает ввод на строки, но мне нужно иметь возможность комбинировать строки чтения и фиксированное количество байтов.

Мой текущий подход заключается в том, что я создаю вспомогательную функцию:

| Сворачивает заданную функцию на входах. Повторяйте, пока функция возвращает Left, и накапливайте ее результаты в списке. Когда функция возвращает Right, объединяем накопленный результат (включая последний) и возвращаем его, сохраняя то, что осталось, используя leftover. Возвращает Nothing, если ввод недоступен.

chunk :: (Monad m, Monoid a)
      => (s -> i -> Either (a, s) (a, i))
      -> s
      -> Consumer i m (Maybe a)
chunk f = loop []
  where
    loop xs s = await >>= maybe (emit xs) (go xs s)

    go xs s i = case f s i of
        Left (x, s')    -> loop (x : xs) s'
        Right (x, l)    -> leftover l >> emit (x : xs)

    emit [] = return Nothing
    emit xs = return (Just . mconcat . L.reverse $ xs)
-- Note: We could use `mappend` to combine the chunks directly. But this would
-- often get us O(n^2) complexity (like for `ByteString`s) so we keep a list of
-- the chunks and then use `mconcat`, which can be optimized by the `Monoid`.

Используя эту функцию, я создаю конкретных потребителей:

bytes :: (Monad m) => Int -> Consumer ByteString m (Maybe ByteString)
bytes = chunk f
  where
    f n bs | n' > 0     = Left (bs, n')
           | otherwise  = Right $ BS.splitAt n bs
      where n' = n - BS.length bs

line :: (Monad m) => Consumer ByteString m (Maybe ByteString)
line = chunk f ()
  where
    f _ bs = maybe (Left (bs, ()))
                   (\i -> Right $ BS.splitAt (i + 1) bs)
                   (BS.findIndex (== '\n') bs)

Есть ли способ лучше? Я полагаю, что эта проблема, должно быть, уже где-то решена.


person Petr    schedule 29.07.2013    source источник
comment
Выглядит правильно, это очень похоже на то, как Warp анализирует заголовки запросов, хотя Warp не возится с какими-либо комбинаторами более высокого уровня.   -  person Michael Snoyman    schedule 30.07.2013
comment
@MichaelSnoyman Спасибо, возможно, добавьте это как ответ, чтобы я мог его принять.   -  person Petr    schedule 30.07.2013


Ответы (1)


Выглядит правильно, это очень похоже на то, как Warp анализирует заголовки запросов, хотя Warp не возится с какими-либо комбинаторами более высокого уровня.

person Michael Snoyman    schedule 30.07.2013