Подтвердить что ты не робот

Haskell IO и файлы закрытия

Когда я открываю файл для чтения в Haskell, я обнаружил, что после его закрытия я не могу использовать содержимое файла. Например, эта программа будет печатать содержимое файла:

main = do inFile <- openFile "foo" ReadMode
          contents <- hGetContents inFile
          putStr contents
          hClose inFile

Я ожидал, что переключение строки putStr с линией hClose не будет иметь никакого эффекта, но эта программа ничего не печатает:

main = do inFile <- openFile "foo" ReadMode
          contents <- hGetContents inFile
          hClose inFile
          putStr contents

Почему это происходит? Я предполагаю, что это имеет какое-то отношение к ленивой оценке, но я думал, что эти выражения будут упорядочены, поэтому проблем не будет. Как бы вы реализовали функцию типа readFile?

4b9b3361

Ответ 1

Как заявили другие, это из-за ленивой оценки. После этой операции рукоятка будет полузамкнута и будет автоматически закрыта при чтении всех данных. Таким образом, 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

В эти дни, однако, никто больше не использует строки для ввода-вывода файлов. Новый способ - использовать Data.ByteString(доступно для взлома) и Data.ByteString.Lazy, когда вы хотите ленивых чтений.

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 - это путь для больших строк (например, содержимого файла). Они намного быстрее и эффективнее памяти, чем String (= [ Char]).

Примечания:

Я импортировал rnf из Control.Parallel.Strategies только для удобства. Вы могли бы написать что-то вроде этого довольно легко:

  forceList [] = ()
  forceList (x:xs) = forceList xs

Это просто приводит к обходу спинного хребта (а не значений) списка, что может повлиять на чтение всего файла.

Ленивый ввод-вывод становится экспертом злым; Я рекомендую использовать строгие байты для большинства операций ввода-вывода файлов. В духовке есть несколько решений, которые пытаются вернуть композиционные инкрементные чтения, наиболее перспективным из которых Олегом называется "Итератез".

Ответ 2

[ Обновление: Prelude.readFile вызывает проблемы, описанные ниже, но переключение на использование версий всех версий .B.BitteString: я больше не получаю исключения.]

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. (Интересно, как хромают сообщения об исключениях Haskell, не имеют номеров строк и т.д.?)

Test> go "Foo.hs"
reading
writing
Exception: Foo.hs: openFile: permission denied (Permission denied)
Test> 

?!?!?

Ответ 3

Это потому, что hGetContents ничего не делает: это ленивый ввод-вывод. Только когда вы используете результирующую строку, файл фактически читается (или его часть, которая необходима). Если вы хотите заставить его читать, вы можете вычислить его длину и использовать функцию seq, чтобы заставить длину оценивать. Lazy I/O может быть классным, но он также может быть запутанным.

Для получения дополнительной информации см. часть о ленивом вводе/выводе в Real World Haskell, например.

Ответ 4

Как отмечалось ранее, 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

Ответ 5

Если вы хотите, чтобы ваш IO ленивый, но чтобы сделать это безопасно, чтобы такие ошибки не возникали, используйте пакет, предназначенный для этого, например safe-lazy-io. (Тем не менее, safe-lazy-io не поддерживает байтов ввода-вывода.)

Ответ 6

Объяснение довольно длинное, чтобы быть включенным здесь. Простите меня за то, что вы раздавали только короткий совет: вам нужно прочитать о "полузакрытых ручках файлов" и "unsafePerformIO".

Вкратце - это поведение является компрометацией дизайна между смысловой ясностью и ленивой оценкой. Вы должны либо отложить hclose, пока не убедитесь, что вы ничего не будете делать с содержимым файла (например, вызовите его в обработчике ошибок или somesuch), или используйте что-то еще, кроме hGetContents, чтобы получить содержимое файла не лениво.