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

HGetContents - слишком ленивый

У меня есть следующий фрагмент кода, который я передаю в withFile:

text <- hGetContents hand 
let code = parseCode text
return code

Здесь рука - это действительный дескриптор файла, открытый с помощью ReadMode и parseCode - это моя собственная функция, которая считывает ввод и возвращает значение Maybe. Как бы то ни было, функция не работает и возвращает Nothing. Если вместо этого я пишу:

text <- hGetContents hand 
putStrLn text
let code = parseCode text
return code

Я получаю Just, как и должен.

Если я сам openFile и hClose, у меня такая же проблема. Почему это происходит? Как я могу это решить?

Спасибо

4b9b3361

Ответ 1

hGetContents не слишком ленив, он просто должен быть скомбинирован с другими вещами, чтобы получить желаемый эффект. Может быть, ситуация была бы яснее, если бы она была переименована exposeContentsToEvaluationAsNeededForTheRestOfTheAction или просто listen.

withFile открывает файл, делает что-то (или ничего, как вам угодно - именно то, что вам нужно от него в любом случае), и закрывает файл.

Едва ли будет достаточно выявить все тайны "ленивого ввода-вывода", но теперь рассмотрим эту разницу в брекетинге

 good file operation = withFile file ReadMode (hGetContents >=> operation >=> print)
 bad file operation = (withFile file ReadMode hGetContents) >>= operation >>= print

-- *Main> good "lazyio.hs" (return . length)
-- 503
-- *Main> bad "lazyio.hs" (return . length)
-- 0

Грубо поставленный, bad открывает и закрывает файл, прежде чем он что-либо сделает; good делает все между открытием и закрытием файла. Ваше первое действие было похоже на bad. withFile должно управлять всем действием, которое вы хотите сделать, которое зависит от дескриптора.

Вам не нужен строгий контроль, если вы работаете с String, небольшими файлами и т.д., просто идея, как работает композиция. Опять же, в bad все, что я делаю, перед закрытием файла exposeContentsToEvaluationAsNeededForTheRestOfTheAction. В good составляю exposeContentsToEvaluationAsNeededForTheRestOfTheAction с остальной частью действия, которое я имею в виду, затем закройте файл.

Знакомый трюк length + seq, упомянутый Патриком, или length + evaluate стоит знать; второе действие с putStrLn txt было вариантом. Но реорганизация лучше, если ленивый IO не подходит для вашего дела.

$ time ./bad
bad: Prelude.last: empty list  
                        -- no, lots of Chars there
real    0m0.087s

$ time ./good
'\n'                -- right
()
real    0m15.977s

$ time ./seqing 
Killed               -- hopeless, attempting to represent the file contents
    real    1m54.065s    -- in memory as a linked list, before finding out the last char

Само собой разумеется, что ByteString и Text заслуживают внимания, но реорганизация с учетом оценки лучше, поскольку даже с ними часто используются варианты Lazy, которые вам нужны, и затем они привлекают к себе одинаковые различия между формами композиции, Если вы имеете дело с одним из (огромных) классов случаев, когда этот тип ввода-вывода неуместен, взгляните на enumerator, conduit и co., Все замечательно.

Ответ 2

hGetContents использует ленивый IO; он только считывает из файла, когда вы заставляете больше строки, и только закрывает дескриптор файла, когда вы оцениваете всю возвращаемую строку. Проблема в том, что вы вставляете его в withFile; вместо этого просто используйте openFile и hGetContents напрямую (или, проще говоря, readFile). Файл будет закрыт после полной оценки строки. Что-то вроде этого должно сделать трюк, чтобы убедиться, что файл полностью прочитан и закрыт немедленно, предварительно запустив всю строку:

import Control.Exception (evaluate)

readCode :: FilePath -> IO Code
readCode fileName = do
    text <- readFile fileName
    evaluate (length text)
    return (parseCode text)

Неинтуитивные ситуации, подобные этому, являются одной из причин, по которым люди склонны избегать ленивого ввода-вывода в эти дни, но, к сожалению, вы не можете изменить определение hGetContents. Строгая версия IO hGetContents доступна в пакете strict, но, вероятно, не стоит в зависимости от пакета только для этой функции.

Если вы хотите избежать накладных расходов, возникающих при перемещении строки дважды здесь, то вам, вероятно, следует изучить более эффективный тип, чем String, в любом случае; тип Text имеет строгие эквиваленты IO для большей части функциональных возможностей ввода-вывода String, как и ByteString (если вы имеете дело с двоичными данными, а не с текстом Unicode).

Ответ 3

Вы можете заставить содержимое text оцениваться с помощью

length text `seq` return code

в качестве последней строки.