Haskell:隐藏懒惰IO中的失败

时间:2013-11-01 20:54:09

标签: haskell lazy-io

这是一个noob问题。

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

imageStream :: [IO Image]

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

readImage :: IO (Maybe Image)

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

maybeImageStream :: [IO (Maybe Image)]

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

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

从语义上讲,当您向flattenImageStream询问下一张图像时,它应该遍历列表并尝试读取每张图像。它会在找到加载并返回的图像之前执行此操作。

编辑:答案中似乎存在一些分歧。 有些人建议使用sequence的解决方案,但我很确定我测试了它并发现它会破坏懒惰。 (我会再次测试它,以确保我何时回到计算机上。) 有人还建议使用unsafeInterleaveIO。 从该函数的文档来看,它似乎可行,但显然我想尽可能地尊重类型系统。

4 个答案:

答案 0 :(得分:9)

您可以使用ListT中的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

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

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

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 ()

如果您想使用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

答案 1 :(得分:1)

如建议你可以使用序列<[p]将[m a]变成m [a]

所以你得到:

imageStream :: IO [Image]

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

catMaybes `liftM` imageStream

答案 2 :(得分:0)

按要求实现这个似乎需要知道IO monad之外是否IO内的值是Nothing,并且IO旨在防止其值“泄漏”到外部纯功能世界(尽管unsafePerformIO),这是不可能的。相反,我建议生成IO [Image]:使用sequence[IO (Maybe Image)]转换为IO [Maybe Image],然后在IO monad中使用Data.Maybe.catMaybes(例如,使用fmap {1}}或liftM)转换为IO [Image],例如:

flattenImageStream = fmap catMaybes $ sequence maybeImageStream

答案 3 :(得分: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 

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