考虑一下这个片段-
getLine >>= \_ -> getLine >>= putStr它做了合理的事情,请求一个字符串两次,然后打印最后一个输入。因为编译器无法知道getLine有什么外部效果,所以它必须同时执行这两个外部效果,即使我们丢弃了第一个外部效果的结果。
我需要的是将IO Monad封装到另一个Monad (M)中,它允许IO计算有效地NOPs,除非它们的返回值被使用。因此上面的程序可以被重写为类似于-
runM $ lift getLine >>= \_ -> lift getLine >>= lift putStr哪里
runM :: M a -> IO a
lift :: IO a -> M a并且用户只被要求输入一次。
然而,我不知道如何写这个Monad来达到我想要的效果。我甚至不确定这是否可能。有没有人能帮帮忙?
发布于 2011-08-25 21:46:42
惰性IO通常是使用unsafeInterleaveIO :: IO a -> IO a实现的,它会延迟IO操作的副作用,直到需要它的结果,所以我们可能不得不使用它,但让我们先解决一些小问题。
首先,lift putStr不会进行类型检查,因为putStr的类型是String -> IO (),而lift的类型是IO a -> M a。我们将不得不使用类似lift . putStr的东西来代替。
其次,我们必须区分应该懒惰的IO操作和不应该懒惰的IO操作。否则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. A. McCann points out,您可能不应该将其用于任何严重的事情。懒惰IO已经不受欢迎了,因为它很难推断出副作用的实际顺序。这将使它变得更加困难。
考虑这个例子
main = runM $ do
foo <- lazy readLn
bar <- lazy readLn
return $ foo / bar这两个数字的读入顺序将完全不确定,可能会根据编译器版本、优化或星号的对齐而变化。unsafeInterleaveIO这个名字又长又丑,有一个很好的理由:提醒您使用它的危险。这是一个好主意,让人们知道它何时被使用,而不是将其隐藏在monad中。
发布于 2011-08-25 21:49:58
没有明智的方法来做这件事,因为老实说,这并不是一件明智的事情。引入一元I/O的整个目的是在存在惰性评估的情况下为效果提供一个定义良好的排序。如果你真的需要的话,当然可以把它抛到窗外,但我不确定这会解决什么实际问题,除了让编写令人困惑的but代码变得更容易之外。
也就是说,以受控的方式引入这类东西是"Lazy IO“已经在做的事情。它的“原始”操作是unsafeInterleaveIO,它大致被实现为return . unsafePerformIO,外加一些细节以使事情表现得更好一些。将unsafeInterleaveIO应用于所有东西,通过将其隐藏在“惰性IO”monad的绑定操作中,可能会实现您想要的不明智的概念。
发布于 2011-08-26 01:09:59
除非你想使用像unsafeInterleaveIO这样不安全的东西,否则你要找的并不是真正的monad。
相反,这里有一个更清晰的抽象: Arrow。
我认为,下面的方法是可行的:
data Promise m a
= Done a
| Thunk (m a)
newtype Lazy m a b =
Lazy { getLazy :: Promise m a -> m (Promise m b) }https://stackoverflow.com/questions/7191058
复制相似问题