6

这是一个菜鸟问题。

我想写一个函数来提供一个懒惰的图像流,大概是这样的:

imageStream :: [IO Image]

不幸的是,读取图像的函数可能会失败,所以它看起来像:

readImage :: IO (Maybe Image)

因此,我可以编写的函数如下所示:

maybeImageStream :: [IO (Maybe Image)]

如何在保持惰性 IO 的同时实现如下功能?

flattenImageStream :: [IO (Maybe Image)] -> [IO Image]

从语义上讲,当您请求flattenImageStream下一张图片时,它应该遍历列表并尝试读取每张图片。它会执行此操作,直到找到加载的图像并将其返回。

编辑:答案似乎存在一些分歧。有些人建议使用sequence. (我会再次测试它以确定何时回到我的电脑。)有人还建议使用unsafeInterleaveIO. 从该函数的文档来看,它似乎可以工作,但显然我想尽可能地尊重类型系统。

4

4 回答 4

9

您可以使用ListTfrom pipes,它为惰性提供了一种更安全的替代方案,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

如果您使用dirstreamlibrary,那么您甚至可以懒惰地流式传输目录内容。

仅过滤出成功结果的函数将具有以下类型:

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具体的。它还保留了懒惰!

应用flattenImageimageStream,给我们一些类型的东西:

finalStream :: List IO Image
finalStream = flattenImage imageStream

现在假设您有一些使用这些图像的函数,类型为:

useImage :: Image -> IO ()

如果要ListT使用useImage函数处理最终结果,只需编写:

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

我也在考虑添加一个fail定义,ListT这样你就可以写:

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
于 2013-11-01T22:18:43.330 回答
1

正如建议的那样,您可以使用序列将 [ma] 变成 m [a]

所以你得到:

imageStream :: IO [Image]

那么您可以使用 Data.Maybe 中的 cayMaybes 来保留 Just 值:

catMaybes `liftM` imageStream
于 2013-11-01T21:09:54.130 回答
0

按要求实现这一点似乎需要在 IO monad 之外知道 IO 内部的值是否NothingunsafePerformIO. 相反,我建议生成一个IO [Image]: 用于sequence转换[IO (Maybe Image)]to IO [Maybe Image],然后Data.Maybe.catMaybes在 IO monad 中使用(例如,使用fmapor liftM)转换为IO [Image],例如:

flattenImageStream = fmap catMaybes $ sequence maybeImageStream
于 2013-11-01T21:08:44.023 回答
0

我认为这些其他答案中的任何一个都不是您想要的。因为我很确定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 

但是你在做什么有点奇怪。如果你有错误的文件路径怎么办?如果图像根本无法加载怎么办?您将尝试永远加载图像。在图像放弃之前,您可能应该多次尝试加载图像。

于 2013-11-01T21:14:07.017 回答