11

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

as <- getLargeList

现在,我正在尝试fn :: a -> IO b申请as

as <- getLargeList
bs <- mapM fn as

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

4

2 回答 2

18

不要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

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

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

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

...然后运行:

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

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

有关更多此类示例,请阅读pipes教程Control.Pipe.Tutorial

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

于 2012-09-26T20:48:28.193 回答
7

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

于 2012-09-26T20:26:04.673 回答