Как лень и I / O работают вместе в Haskell? - PullRequest
10 голосов
/ 06 мая 2010

Я пытаюсь глубже понять лень в Хаскеле.

Сегодня я представлял себе следующий фрагмент:

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

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

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

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

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

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

Ответы [ 2 ]

17 голосов
/ 06 мая 2010

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

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


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

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

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

9 голосов
/ 06 мая 2010

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

Книга о реальном мире Haskell содержит длинную трактовку ленивого ввода / вывода и затрагивает некоторые подводные камни.

...