При работе с потоковыми библиотеками обычно плохая идея повторно использовать эффективный поток. Таким образом, вы можете применить функцию типа drop
или splitAt
к потоку и затем продолжить работу с результирующим потоком, или вы можете использовать поток целиком с помощью функции, подобной сбросьте , что оставляет вас в базовой монаде. Но вы никогда не должны применять одно и то же значение потока к двум разным функциям.
К сожалению, система типов Haskell в ее нынешнем виде не способна применить это ограничение во время компиляции, для нее потребуется некоторая форма линейных типов . Вместо этого это становится ответственностью пользователя.
Функция null_
, возможно, является бородавкой в API-интерфейсе streaming-bytestring *, поскольку она не возвращает новый поток вместе с результатом, создавая впечатление, что поток повторное использование является нормальным явлением во всем API. Было бы лучше, если бы у него была подпись вроде
null_ :: ByteString m r -> m (Bool, ByteString m r)
.
Аналогично, не используйте drop
и take
с одинаковым значением потока. Вместо этого используйте splitAt
или uncons
и работайте с разделенным результатом.
dump :: MonadIO m => BSS.ByteString m r -> m ()
dump bs = do
mc <- BSSC.uncons bs -- bs is only used once
case mc of
Left _ -> return ()
Right (c,rest) -> do liftIO $ putChar c
dump rest
Итак, об ошибке. Как отмечает @BobDalgleish в комментариях, происходит то, что файл открывается при вызове null_
(это первый раз, когда мы «требуем» что-то из потока). В рекурсивном вызове мы снова передаем исходное значение bs
, поэтому он будет снова открывать файл, один раз для каждой итерации, пока мы не достигнем предела дескриптора файла.
Лично я не фанат использования ResourceT
с потоковыми библиотеками. Я предпочитаю открыть файл с withFile
, а затем создать и использовать поток с обратным вызовом, если это возможно. Но некоторые вещи сложнее.