Ленивый ввод-вывод обычно реализуется с использованием 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
длинное и безобразное по уважительной причине: чтобы напомнить вам об опасностях его использования. Хорошая идея - дать людям знать, когда он используется, а не прятать его в монаде.