考虑以下简单的IO功能:
req :: IO [Integer] req = do print "x" return [1,2,3]
实际上,这可能是一个http请求,它在解析结果后返回一个列表.
我试图以懒惰的方式连接该函数的几个调用的结果.
简单来说,以下内容应仅打印'x'两次:
fmap (take 4) req' --> [1, 2, 3, 4]
我认为这可以用sequence
或者解决mapM
,但是我的方法在懒惰方面失败了:
import Control.Monad req' :: IO [Integer] req' = fmap concat $ mapM req [1..1000] -- should be infinite..
这会产生正确的结果,但是IO函数req被调用1000次而不是必需的2次.当使用无限列表上的映射实现上述内容时,评估根本不会终止.
您不应该这样做,查看流式IO库,例如pipes
或conduit
代替.
你不能.或者至少,你真的不应该.允许懒惰评估的代码有副作用通常是一个非常糟糕的主意.不仅很快就会很难推断出效果何时以及执行了多少次,但更糟糕的是,效果可能无法按照您期望的顺序执行!使用纯代码,这不是什么大问题.使用副作用代码,这是一场灾难.
想象一下,您想要从引用中读取值,然后用更新的值替换该值.在IO monad中,计算顺序很明确,这很容易:
main = do yesterdaysDate <- readIORef ref writeIORef ref todaysDate
但是,如果上面的代码是懒惰地进行评估,则不能保证在写入之前读取引用 - 或者甚至根本不执行两个计算.程序的语义完全取决于我们是否以及何时需要计算结果.这是首先提出monad的原因之一:为程序员提供一种编写带副作用的代码的方法,这种副作用以明确定义且易于理解的顺序执行.
现在,它是实际上可以懒洋洋地串联列表,如果你使用创建它们unsafeInterleaveIO
:
import System.IO.Unsafe req :: IO [Integer] req = unsafeInterleaveIO $ do print "x" return [1,2,3] req' :: IO [Integer] req' = fmap concat $ mapM (const req) [1..1000]
这将导致每个应用程序req
被推迟,直到需要相应的子列表.然而,像这样懒洋洋地执行IO可能会导致有趣的竞争条件和资源泄漏,并且通常不赞成.建议的替代方法是使用评论中提到的流式IO库,如conduit
或pipes
.