当前位置:  开发笔记 > 程序员 > 正文

使用mapM保留懒惰

如何解决《使用mapM保留懒惰》经验,为你挑选了1个好方法。

考虑以下简单的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次.当使用无限列表上的映射实现上述内容时,评估根本不会终止.



1> valderman..:

精简版:

您不应该这样做,查看流式IO库,例如pipesconduit代替.

长版:

你不能.或者至少,你真的不应该.允许懒惰评估的代码有副作用通常是一个非常糟糕的主意.不仅很快就会很难推断出效果何时以及执行了多少次,但更糟糕的是,效果可能无法按照您期望的顺序执行!使用纯代码,这不是什么大问题.使用副作用代码,这是一场灾难.

想象一下,您想要从引用中读取值,然后用更新的值替换该值.在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库,如conduitpipes.

推荐阅读
手机用户2402852307
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有