【问题标题】:lazy version of mapMmapM 的懒惰版本
【发布时间】:2012-09-18 12:29:29
【问题描述】:

假设,我在使用 IO 时得到大量项目:

as <- getLargeList

现在,我正在尝试将fn :: a -&gt; IO b 应用到as

as <- getLargeList
bs <- mapM fn as

mapM 的类型为mapM :: Monad m =&gt; (a -&gt; m b) -&gt; [a] -&gt; m [b],这就是我在类型匹配方面所需要的。但它会在内存中构建所有链,直到返回结果。我正在寻找mapM 的类似物,它可以懒惰地工作,这样我就可以在tail 仍在构建时使用bs 的头部。

【问题讨论】:

  • Anton,我已经阅读了这个主题,但我没有找到答案:对于惰性计算是否有 mapM 的替代方案。
  • @DmitryBespalov 不具有相同的类型签名。 Monad 没有将效果推迟到以后的抽象 - 这就是你必须做的事情才能让 mapM 变得更懒惰。

标签: haskell io lazy-sequences


【解决方案1】:

不要为此使用unsafeInterleaveIO 或任何惰性IO。这正是创建迭代器要解决的问题:避免惰性 IO,这会提供不可预测的资源管理。诀窍是从不构建列表,并不断使用迭代器对其进行流式传输,直到您完成使用它为止。我将使用我自己的库 pipes 中的示例来演示这一点。

首先,定义:

import Control.Monad
import Control.Monad.Trans
import Control.Pipe

-- Demand only 'n' elements
take' :: (Monad m) => Int -> Pipe a a m ()
take' n = replicateM_ n $ do
    a <- await
    yield a

-- Print all incoming elements
printer :: (Show a) => Consumer a IO r
printer = forever $ do
    a <- await
    lift $ print a

现在让我们对我们的用户刻薄,并要求他们为我们生成非常大的列表:

prompt100 :: Producer Int IO ()
prompt100 = replicateM_ 1000 $ do
    lift $ putStrLn "Enter an integer: "
    n <- lift readLn
    yield n

现在,让我们运行它:

>>> runPipe $ printer <+< take' 1 <+< prompt100
Enter an integer:
3<Enter>
3

它只提示用户输入一个整数,因为我们只要求一个整数!

如果你想用getLargeList 的输出替换prompt100,你只需写:

yourProducer :: Producer b IO ()
yourProducer = do
    xs <- lift getLargeList
    mapM_ yield xs

...然后运行:

>>> runPipe $ printer <+< take' 1 <+< yourProducer

这将懒惰地流式传输列表并且永远不会在内存中构建列表,所有这些都无需使用不安全的IO hacks。要更改您需要多少元素,只需更改您传递给take'的值

如需更多此类示例,请阅读pipes tutorialControl.Pipe.Tutorial

要详细了解为什么惰性 IO 会导致问题,请阅读 Oleg 关于该主题的原始幻灯片,您可以找到 here。他很好地解释了使用惰性 IO 的问题。任何时候你不得不使用惰性 IO,你真正想要的是一个迭代库。

【讨论】:

  • 加布里埃尔,谢谢您的回答!这是一个非常有用的库,谢谢!
【解决方案2】:

IO monad 确实有延迟效果的机制。它被称为unsafeInterleaveIO。您可以使用它来获得想要的效果:

import System.IO.Unsafe

lazyMapM :: (a -> IO b) -> [a] -> IO [b]
lazyMapM f [] = return []
lazyMapM f (x:xs) = do y <- f x
                       ys <- unsafeInterleaveIO $ lazyMapM f xs
                       return (y:ys)

这就是惰性 IO 的实现方式。实际执行效果的顺序很难预测并且将由结果列表中元素的评估顺序决定,这是不安全的。出于这个原因,重要的是f 中的任何 IO 影响都是良性的,因为它们应该是顺序不敏感的。从只读文件中读取通常是非常良性的效果的一个很好的例子。

【讨论】:

  • 在我看来,在 Haskell 中使用 System.IO.Unsafe 有点像 hack。我宁愿避免使用它,因为它的“不安全”行为。不过,我很感激你的回答。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-06
  • 2011-07-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多