Как лень и ввод-вывод сотрудничают в Haskell?

Я пытаюсь получить более глубокое понимание лени в Haskell.

Сегодня я воображал следующий отрывок:

data Image = Image { name :: String, pixels :: String }

image :: String -> IO Image
image path = Image path <$> readFile path

Обращение здесь состоит в том, что я мог просто создать экземпляр Изображения и раздать его; если бы мне нужны данные изображения, они были бы считаны лениво - в противном случае, времени и стоимости памяти чтения файла избегут:

 main = do
   image <- image "file"
   putStrLn $ length $ pixels image

Но это, как это на самом деле работает? Как лень совместима с IO? Будет readFile быть названным независимо от того, получаю ли я доступ pixels image или время выполнения оставит того преобразователя неоцененным, если я никогда не обращусь к нему?

Если изображение действительно прочитано лениво, то не является этим, возможные действия ввода-вывода могли бы произойти не в порядке? Например, что, если сразу после вызова image Я удаляю файл? Теперь вызов putStrLn ничего не найдет, когда он попытается читать.

10
задан Bill 6 May 2010 в 00:47
поделиться

2 ответа

Как лень совместим с вводом-выводом?

Краткий ответ: Нет.


Длинный ответ: Операции ввода-вывода строго упорядочены, в основном по причинам, о которых вы думаете. Любые чистые вычисления, сделанные с результатами, конечно, могут быть ленивыми; например, если вы читаете файл, выполняете некоторую обработку, а затем распечатываете некоторые результаты, вполне вероятно, что любая обработка, которая не требуется для вывода, не будет оцениваться. Однако будет прочитан весь файл, даже части, которые вы никогда не используете. Если вам нужен ленивый ввод-вывод, у вас есть примерно два варианта:

  • Выполните собственные явные процедуры отложенной загрузки и тому подобное, как если бы вы делали это в любом строгом языке. Конечно, это кажется раздражающим, но, с другой стороны, Haskell делает прекрасный строгий, императивный язык. Если вы хотите попробовать что-то новое и интересное, попробуйте найти Iteratees .

  • Обманывать как читер. Функции , такие как hGetContents , будут выполнять ленивый ввод-вывод по запросу без лишних вопросов. В чем подвох? Это (технически) нарушает ссылочную прозрачность. Чистый код может косвенно вызывать побочные эффекты, и могут происходить забавные вещи, связанные с упорядочиванием побочных эффектов, если ваш код действительно запутан. hGetContents и его друзья реализованы с использованием unsafeInterleaveIO , что ... в точности то, что написано на банке. Вероятность взрыва не так велика, как использование unsafePerformIO , но считайте себя предупрежденным.

17
ответ дан 3 December 2019 в 16:09
поделиться

Ленивый ввод-вывод нарушает чистоту Haskell. Результаты выполнения readFile действительно производятся лениво, по требованию. Порядок выполнения действий ввода-вывода не фиксирован, поэтому да, они могут выполняться "не по порядку". Проблема удаления файла перед извлечением пикселей вполне реальна. Короче говоря, ленивый ввод-вывод - это большое удобство, но это инструмент с очень острыми краями".

В книге "Real World Haskell" есть подробное описание ленивого ввода-вывода и некоторые подводные камни.

9
ответ дан 3 December 2019 в 16:09
поделиться
Другие вопросы по тегам:

Похожие вопросы: