这是一个菜鸟问题。
我想编写一个提供惰性图像流的函数,大概是这样的:
imageStream :: [IO Image]
不幸的是,读取图像的函数可能会失败,因此它看起来像:
readImage :: IO (Maybe Image)
因此,我可以编写的函数如下所示:
maybeImageStream :: [IO (Maybe Image)]
如何在保持惰性 IO 的同时实现如下功能?
flattenImageStream :: [IO (Maybe Image)] -> [IO Image]
从语义上讲,当您向flattenImageStream
请求下一个图像时,它应该循环访问列表并尝试读取每个图像。它会执行此操作,直到找到加载的图像并返回它。
编辑:答案中似乎存在一些分歧。有些人提出了使用sequence
的解决方案,但我很确定我测试过并发现它破坏了懒惰。(我将再次测试它,以确保何时返回计算机。有人还建议使用unsafeInterleaveIO
.从该函数的文档来看,它似乎可以工作,但显然我想尽可能地尊重类型系统。
pipes
中的ListT
,它为在这种情况下做正确事情的懒惰IO
提供了一种更安全的替代方案。
对可能失败的图像的延迟流进行建模的方式是:
imageStream :: ListT IO (Maybe Image)
假设您有一些类型的图像加载函数:
loadImage :: FileName -> IO (Maybe Image)
.. 那么你构建这样一个流的方式是这样的:
imageStream = do
fileName <- Select $ each ["file1.jpg", "file2.jpg", "file3.jpg"]
lift $ loadImage fileName
如果您使用 dirstream
库,那么您甚至可以懒惰地流式传输目录内容。
仅筛选出成功结果的函数将具有以下类型:
flattenImageStream :: (Monad m) => ListT m (Maybe a) -> ListT m a
flattenImageStream stream = do
ma <- stream
case ma of
Just a -> return a
Nothing -> mzero
请注意,此函数适用于任何基 monad,m
。 没有什么IO
具体的。 它还保留了懒惰!
将flattenImage
应用于 imageStream
,为我们提供了一些类型:
finalStream :: List IO Image
finalStream = flattenImage imageStream
现在,假设您有一些函数使用这些类型的图像:
useImage :: Image -> IO ()
如果你想使用 useImage
函数处理最终的ListT
,你只需编写:
main = runEffect $
for (every finalStream) $ image -> do
lift $ useImage image
然后,这将延迟使用图像流。
当然,您也可以玩代码高尔夫,并将所有这些组合成以下更短的版本:
main = runEffect $ for (every image) (lift . useImage)
where
image = do
fileName <- Select $ each ["file1.jpg", "file2.jpg", "file3.jpg"]
maybeImage <- lift $ loadImage fileName
case maybeImage of
Just img -> return img
Nothing -> mzero
我还在考虑为ListT
添加一个fail
定义,以便您可以编写:
main = runEffect $ for (every image) (lift . useImage)
where
image = do
fileName <- Select $ each ["file1.jpg", "file2.jpg", "file3.jpg"]
Just img <- lift $ loadImage fileName
return img
按照建议,您可以使用序列将 [m a] 转换为 m [a]
所以你得到:
imageStream :: IO [Image]
然后,您可以使用 cayMaybes from Data.也许只保留 Just 值:
catMaybes `liftM` imageStream
按照要求实现这一点似乎需要在IO monad之外知道IO内部的值是否Nothing
,并且由于IO旨在防止其值"泄漏"到外部纯功能世界中(尽管unsafePerformIO
),这是不可能的。 相反,我建议生成一个IO [Image]
:使用 sequence
将[IO (Maybe Image)]
转换为 IO [Maybe Image]
,然后在 IO monad 中使用 Data.Maybe.catMaybes
(例如,使用 fmap
或 liftM
)转换为IO [Image]
,例如:
flattenImageStream = fmap catMaybes $ sequence maybeImageStream
我认为这些其他答案都没有完全按照您的要求。 因为我很确定catMaybes
只会跳过图像而不会尝试重新加载它。 如果您只想继续尝试重新加载图像,请尝试此操作。
flattenImageStream :: [IO (Maybe Image)] -> IO [Image]
flattenImageStream xs = mapM untilSuc xs
untilSuc :: IO (Maybe a) -> IO a
untilSuc f = do
res <- f
case res of
Nothing -> untilSuc f
Just i -> return i
但是你正在做的事情有点奇怪。 如果文件路径错误怎么办? 如果图像根本无法加载怎么办? 您只需尝试永久加载图像。 在图像放弃之前,您可能应该有很多次尝试加载图像。