Вам не нужно использовать какой-либо особый способ выполнения ввода-вывода, вам просто нужно изменить порядок, в котором вы делаете вещи. Поэтому вместо того, чтобы открывать все файлы и затем обрабатывать содержимое, вы открываете один файл и печатаете одну строку вывода за раз.
import Data.Digest.Pure.MD5 (md5)
import qualified Data.ByteString.Lazy as BS
main :: IO ()
main = mapM_ (\path -> putStrLn . fileLine path =<< BS.readFile path)
=<< getRecursiveContents "."
fileLine :: FilePath -> BS.ByteString -> String
fileLine path c = hash c ++ " " ++ path
hash :: BS.ByteString -> String
hash = show . md5
Кстати, я использую другую хэш-библиотеку md5, разница не значительна.
Главное, что здесь происходит, это строка:
mapM_ (\path -> putStrLn . fileLine path =<< BS.readFile path)
Он открывает один файл, потребляет все содержимое файла и печатает одну строку вывода. Он закрывает файл, потому что он потребляет все содержимое файла. Ранее вы откладывали, когда файл был использован, что задерживалось, когда файл был закрыт.
Если вы не совсем уверены, используете ли вы все входные данные, но хотите убедиться, что файл все равно закрывается, вы можете использовать функцию withFile
из System.IO
:
mapM_ (\path -> withFile path ReadMode $ \hnd -> do
c <- BS.hGetContents hnd
putStrLn (fileLine path c))
Функция withFile
открывает файл и передает дескриптор файла в функцию body. Это гарантирует, что файл будет закрыт при возвращении тела. Этот паттерн "withBlah" очень распространен при работе с дорогими ресурсами. Этот шаблон ресурса напрямую поддерживается System.Exception.bracket
.