Haskell:在惰性IO中隐藏故障



这是一个菜鸟问题。

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

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(例如,使用 fmapliftM)转换为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 

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

最新更新