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