Как другие заявили, это из-за отложенных вычислений. Дескриптор является полузамкнутым после этой операции и будет закрыт автоматически, когда все данные считаны. И hGetContents и readFile ленивы таким образом. В случаях, где у Вас есть проблемы с дескрипторами, сохраняемыми открытыми, обычно Вы просто вызываете чтение. Вот простой способ:
import Control.Parallel.Strategies (rnf)
-- rnf means "reduce to normal form"
main = do inFile <- openFile "foo"
contents <- hGetContents inFile
rnf contents `seq` hClose inFile -- force the whole file to be read, then close
putStr contents
В эти дни, однако, никто не использует строки для файлового ввода-вывода больше. Новый путь состоит в том, чтобы использовать Данные. ByteString (доступный на hackage), и Данные. ByteString. Ленивый, когда Вы хотите ленивые чтения.
import qualified Data.ByteString as Str
main = do contents <- Str.readFile "foo"
-- readFile is strict, so the the entire string is read here
Str.putStr contents
ByteStrings являются способом пойти для больших строк (как содержание файла). Они намного быстрее и больше памяти, эффективной, чем Строка (= [Символ]).
Примечания:
я импортировал rnf из Управления. Параллель. Стратегии только удобства. Вы могли записать что-то как он сами довольно легко:
forceList [] = ()
forceList (x:xs) = forceList xs
Это просто вызывает обход позвоночника (не значения) списка, который имел бы эффект чтения целого файла.
Ленивый ввод-вывод становится продуманным злом экспертами; я рекомендую использовать строгие строки байтов для большей части файлового ввода-вывода в настоящее время. Существует несколько решений в духовке, которые пытаются возвратить компонуемые возрастающие чтения, самое многообещающее из которых называет "Iteratee" Oleg.
[ Обновление : Prelude.readFile вызывает проблемы, как описано ниже, но переключающийся на использование Данных. Версии ByteString всего работы: Я больше не получаю исключение.]
новичок Haskell здесь, но в настоящее время я не покупаю заявление, которое "readFile строго, и закрывает файл, когда он сделан":
go fname = do
putStrLn "reading"
body <- readFile fname
let body' = "foo" ++ body ++ "bar"
putStrLn body' -- comment this out to get a runtime exception.
putStrLn "writing"
writeFile fname body'
return ()
, Который работает как есть на файле, который я тестировал с, но если Вы комментируете putStrLn тогда, по-видимому, сбои writeFile. (Интересный, как сообщения об исключениях lame Haskell, испытывая недостаток в номерах строки и т.д.?)
Test> go "Foo.hs"
reading
writing
Exception: Foo.hs: openFile: permission denied (Permission denied)
Test>
?!?!?
Это вызвано тем, что hGetContents ничего еще не делает: это - ленивый ввод-вывод. Только при использовании строки результата, файл на самом деле читается (или часть его, которая необходима). Если Вы хотите вынудить его быть считанным, можно вычислить его длину и использовать функцию seq, чтобы вынудить длину быть оцененной. Ленивый ввод-вывод может быть прохладным, но это может также сбивать с толку.
Для получения дополнительной информации, см. часть о ленивом вводе-выводе в Реальном мире Haskell, например.
Как ранее отмечено, hGetContents
лениво. readFile
строго, и закрывает файл, когда он сделан:
main = do contents <- readFile "foo"
putStr contents
урожаи следующее в Объятиях
> main
blahblahblah
, где foo
blahblahblah
Интересно, seq
, только гарантирует, что некоторая часть из входа читается, не все это:
main = do inFile <- openFile "foo" ReadMode
contents <- hGetContents $! inFile
contents `seq` hClose inFile
putStr contents
урожаи
> main
b
А хороший ресурс: Создание программы Haskell, быстрее и меньшие: hGetContents, hClose, readFile
Объяснение довольно долго, чтобы быть включенным здесь. Простите мне за распределение короткой подсказки только: необходимо читать о "полузакрытых дескрипторах файлов" и "unsafePerformIO".
Короче говоря - это поведение является компромиссом дизайна между семантической четкостью и отложенными вычислениями. Необходимо или отложить hClose, пока Вы не абсолютно уверены Вы woudnt сделать что-либо с содержанием файла (как, назовите его в обработчике ошибок или somesuch), или используйте что-то еще помимо hGetContents для получения содержания файла нелениво.