Haskell: лениво читать двоичный файл с двоичным

Я пытаюсь прочитать в двоичном файле и лениво разобрать его с помощью пакета "двоичного". В документации к пакету приведен пример того, как это сделать без принудительного ввода всех данных для сценария, очень похожего на мой:

 example2 :: BL.ByteString -> [Trade]
 example2 input
  | BL.null input = []
  | otherwise =
    let (trade, rest, _) = runGetState getTrade input 0
    in trade : example2 rest

Тем не менее, это использует устаревший runGetState функция, которая сама указывает вам на runGetIncremental функция.

Проблема в том, что функция runGetIncremental, кажется, заставляет оставшийся ввод быть строгой строкой тестирования, тем самым вынуждая его загружать весь файл в память. Действительно, я вижу использование памяти около 6 ГБ, когда я пытаюсь запустить это. Даже реализация runGetState теперь, кажется, основано на runGetIncremental и затем преобразовывает строгую байтовую строку обратно в ленивую, используя chunk,

Могу ли я получить поведение, описанное в руководстве, или оно теперь не поддерживается двоичным файлом? Если последнее, каков наилучший способ сделать это? У меня есть небольшой опыт использования кабелепровода, но мне не ясно, как я мог бы использовать его здесь.

1 ответ

Решение

Вы можете сделать это используя pipes-binary а также pipes-bytestring, Вот вспомогательная функция для вашей выгоды:

import Control.Monad (void)
import Data.Binary
import Pipes
import Pipes.Binary (decodeMany)
import Pipes.ByteString (fromHandle)
import qualified Pipes.Prelude as P
import System.IO

decodeHandle :: (Binary a) => Handle -> Producer a IO ()
decodeHandle handle = void $ decodeMany (fromHandle handle) >-> P.map snd

void а также map snd потому что decodeMany фактически возвращает больше информации (например, смещение байтов и ошибки синтаксического анализа). Если вы действительно хотите эту информацию, просто удалите ее.

Вот пример того, как вы можете использовать decodeHandle, используя быстрый скелет для Trade Я бросил вместе:

data Trade = Trade

instance Binary Trade where
    get   = return Trade
    put _ = return ()

instance Show Trade where show _ = "Trade"

main = withFile "inFile.txt" ReadMode $ \handle -> runEffect $
    for (decodeHandle handle) $ \trade -> do
        lift $ print (trade :: Trade)
        -- do more with the parsed trade

Ты можешь использовать for чтобы перебрать декодированные сделки и обработать их, или, если вы предпочитаете, вы можете использовать состав трубы:

main = withFile "inFile.txt" ReadMode $ \handle -> runEffect $
    decodeHandle handle >-> P.print

Это будет лениво и расшифрует столько сделок, сколько вам нужно. Так что если вы вставите take между декодером и принтером он будет считывать только столько входных данных, сколько необходимо для обработки запрошенного количества сделок:

main = withFile "inFile.txt" ReadMode $ \handle -> runEffect $
    for (decodeHandle handle >-> P.take 4) $ \trade -> do
        ... -- This will only process the first 4 trades

-- or using purely pipe composition:

main = withFile "inFile.txt" ReadMode $ \handle -> runEffect $
    decodeHandle handle >-> P.take 4 >-> P.print
Другие вопросы по тегам