WithFile vs. openFile
Эта программа производит вывод, который я ожидаю, когда задан входной файл текста, ограниченный \n:
import System.IO
main :: IO ()
main = do h <- openFile "test.txt" ReadMode
xs <- getlines h
sequence_ $ map putStrLn xs
getlines :: Handle -> IO [String]
getlines h = hGetContents h >>= return . lines
Подставляя File для openFile и слегка переставляя
import System.IO
main :: IO ()
main = do xs <- withFile "test.txt" ReadMode getlines
sequence_ $ map putStrLn xs
getlines :: Handle -> IO [String]
getlines h = hGetContents h >>= return . lines
Мне не удается получить никакого вывода. Я в тупике.
Редактирование: больше не зацикливается: благодаря одному и тому же задумчивым и заставляющим задуматься ответам. Я немного больше читал в документации и узнал, что withFile можно понимать как частичное приложение скобки.
Вот что я закончил:
import System.IO
main :: IO ()
main = withFile "test.txt" ReadMode $ \h -> getlines h >>= mapM_ putStrLn
getlines :: Handle -> IO [String]
getlines h = lines `fmap` hGetContents h
Ответы
Ответ 1
Файл закрывается слишком рано. Из документа :
Ручка будет закрыта при выходе из withFile
Это означает, что файл будет закрыт, как только функция withFile
вернется.
Поскольку hGetContents
и друзья ленивы, он не будет пытаться прочитать файл, пока он не будет принудительно с putStrLn
, но к тому времени withFile
уже закрыл файл.
Чтобы решить проблему, передайте все это withFile
:
main = withFile "test.txt" ReadMode $ \handle -> do
xs <- getlines handle
sequence_ $ map putStrLn xs
Это работает, потому что к моменту времени withFile
приближается к закрытию файла, вы уже напечатали его.
Ответ 2
Ух, никто не давал простого решения?
main :: IO ()
main = do xs <- fmap lines $ readFile "test.txt"
mapM_ putStrLn xs
Не используйте openFile
+ hGetContents
или withFile
+ hGetContents
, когда вы можете просто использовать readFile
. С помощью readFile
вы не можете стрелять в ногу, закрыв файл слишком рано.
Ответ 3
Они делают совершенно разные вещи.
openFile
открывает файл и возвращает дескриптор файла:
openFile :: FilePath -> IOMode -> IO Handle
withFile
используется для обертывания вычисления ввода-вывода, которое принимает дескриптор файла, гарантируя, что дескриптор закрывается впоследствии:
withFile :: FilePath -> IOMode -> (Handle -> IO r) -> IO r
В вашем случае использование withFile будет выглядеть так:
main = withFile "test.txt" ReadMode $ \h -> do
xs <- getlines h
sequence_ $ map putStrLn xs
В текущей версии вы откроете файл, вызовите getlines
, затем закройте файл. Поскольку getlines
ленив, он не будет читать какой-либо вывод, пока файл открыт, и как только файл будет закрыт, он не сможет.
Ответ 4
Вы столкнулись с обычными препятствиями ленивого IO... ленивый IO звучит как отличная идея, создавая потоковое воспроизведение, пока вы не начнете получать эти ужасные проблемы.
Не то, чтобы ваш конкретный случай не был красной серой для опытного Haskeller: это пример учебника о том, почему ленивый IO является проблемой.
main = do xs <- withFile "test.txt" ReadMode getlines
sequence_ $ map putStrLn xs
withFile принимает FilePath, режим и действие, выполняемое с дескриптором в результате открытия этого пути к файлу в этом режиме. Интересная часть fromFile заключается в том, что она реализована с помощью скобки и гарантирует, даже в случае исключения, что файл будет закрыт после выполнения действия на дескрипторе. Проблема здесь в том, что действие, о котором идет речь (getLines), вообще не читает файл! Это только обещание сделать это, когда контент действительно необходим! Это ленивый IO (реализованный с unsafeInterleaveIO, угадайте, что означает "небезопасная" часть...). Конечно, к тому времени, когда это содержимое необходимо (putStrLn), дескриптор был закрыт с помощью файла, как и было обещано.
Итак, у вас есть несколько решений: вы можете открыто и закрывать явным образом (и отказаться от безопасности исключений), или вы можете использовать ленивый ввод-вывод, но все действия касаются содержимого файла в области, защищенной с помощью файла:
main = withFile "test.txt" ReadMode$ \h -> do
xs <- getlines h
mapM_ putStrLn xs
В этом случае это не слишком ужасно, но вы должны увидеть, что проблема может стать более раздражающей, если вы проигнорируете, когда потребуется контент. Lazy IO в большой и сложной программе может быстро стать довольно раздражающей, и когда дальнейшее ограничение количества открытых дескрипторов начинает иметь значение... Вот почему новый вид спорта сообщества Haskell заключается в том, чтобы придумать решения проблемы потокового контента (вместо того, чтобы читать целые файлы в памяти, которые "решают" проблему ценой раздувающейся памяти, иногда до невозможных уровней) без ленивого ввода-вывода. Какое-то время казалось, что Iteratee станет стандартным решением, но это было очень сложно и трудно понять даже для опытного Haskeller, поэтому в последнее время подкрались другие кандидаты: наиболее перспективные или, по крайней мере, успешные в настоящее время, похоже, be "кабелепровод" .
Ответ 5
Как отмечали другие, hGetContents
ленив. Однако вы можете добавить строгость, если хотите:
import Control.DeepSeq
forceM :: (NFData a, Monad m) => m a -> m a
forceM m = do
val <- m
return $!! val
main = do xs <- withFile "text.txt" ReadMode (forceM . getlines)
...
Хотя обычно рекомендуется выполнять все IO, связанные с содержимым файла внутри блока withFile
. Таким образом, ваша программа может фактически использовать чтение ленивого файла, сохраняя только столько, сколько необходимо в памяти. Если вы имеете дело с очень большим файлом, то принудительное чтение всего файла в память обычно плохое.
Если вам нужен более мелкомасштабный контроль ресурсов, вам следует изучить ResourceT
(который поставляется с conduit пакет) или аналогичный.
[edit: используйте $!!
от Control.DeepSeq
(вместо $!
), чтобы убедиться, что все значение принудительно. Спасибо за подсказку, @benmachine]