«Истинно» ленивый IO в Хаскеле - PullRequest
6 голосов
/ 25 августа 2011

Рассмотрим фрагмент -

getLine >>= \_ -> getLine >>= putStr

Делает разумную вещь: дважды запрашивает строку, а затем печатает последний ввод. Поскольку компилятор не может узнать, какие внешние эффекты getLine имеет, он должен выполнить их оба, даже если мы отбрасываем результат первого.

Что мне нужно, это заключить монаду ввода-вывода в другую монаду (M), которая позволяет вычислениям ввода-вывода быть фактически NOP, если только не используются их возвращаемые значения. Чтобы вышеприведенная программа могла быть переписана как-то вроде -

runM $ lift getLine >>= \_ -> lift getLine >>= lift putStr

Где

runM :: M a -> IO a
lift :: IO a -> M a

А пользователя просят ввести только один раз.

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

Ответы [ 3 ]

11 голосов
/ 25 августа 2011

Ленивый ввод-вывод обычно реализуется с использованием unsafeInterleaveIO :: IO a -> IO a, что задерживает побочные эффекты действия ввода-вывода до тех пор, пока не потребуется его результат, поэтому нам, вероятно, придется его использовать, но сначала давайте разберемся с некоторыми незначительными проблемами.

Прежде всего, lift putStr не будет проверять тип, поскольку putStr имеет тип String -> IO (), а lift имеет тип IO a -> M a. Вместо этого нам придется использовать что-то вроде lift . putStr.

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

Принимая это во внимание, кажется, это работает для вашего простого примера, по крайней мере.

{-# LANGUAGE GeneralizedNewtypeDeriving #-}

import System.IO.Unsafe

newtype M a = M { runM :: IO a }
    deriving (Monad)

lazy :: IO a -> M a
lazy = M . unsafeInterleaveIO

lift :: IO a -> M a
lift = M

main = runM $ lazy getLine >> lazy getLine >>= lift . putStr

Однако, как C. А. Макканн указывает на , вы, вероятно, не должны использовать это для чего-то серьезного. Ленивый IO уже осужден, поскольку это затрудняет рассуждение о фактическом порядке побочных эффектов. Это сделало бы это еще сложнее.

Рассмотрим этот пример

main = runM $ do
    foo <- lazy readLn
    bar <- lazy readLn
    return $ foo / bar

Порядок чтения двух чисел будет полностью неопределенным и может изменяться в зависимости от версии компилятора, оптимизации или выравнивания звезд. Имя unsafeInterleaveIO длинное и безобразное по уважительной причине: чтобы напомнить вам об опасностях его использования. Хорошая идея - дать людям знать, когда он используется, а не прятать его в монаде.

8 голосов
/ 25 августа 2011

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

Тем не менее, введение такого рода«Контролируемый» - это то, что «Ленивый ИО» уже делает.«Примитивной» операцией для этого является unsafeInterleaveIO, которая реализована примерно как return . unsafePerformIO, плюс некоторые детали, чтобы сделать поведение немного лучше.Применение unsafeInterleaveIO ко всему, сокрытие его в операции связывания вашей монады «ленивый ввод-вывод», вероятно, дало бы неверное представление о том, что вы ищете.

5 голосов
/ 25 августа 2011

То, что вы ищете, на самом деле не является монадой, если только вы не хотите работать с небезопасными вещами, такими как unsafeInterleaveIO.

Вместо этого, гораздо более чистая абстракция здесь - Стрелка.
Я думаю, что следующие могут работать:

data Promise m a
    = Done a
    | Thunk (m a)

newtype Lazy m a b =
    Lazy { getLazy :: Promise m a -> m (Promise m b) }
...