【问题标题】:Create lazy IO list from a non-IO list从非 IO 列表创建惰性 IO 列表
【发布时间】:2013-04-26 19:26:17
【问题描述】:

我有一个 lazyfind 创建的文件名列表。我也希望能够懒惰地加载这些文件的元数据。这意味着,如果我来自metadatatake 10 元素,它应该只搜索这十个文件的元数据。事实上,find 完美地为您提供了 10 个文件,如果您在不挂磁盘的情况下要求它们,而我的脚本会搜索所有文件的元数据。

main = do
    files <- find always always / 
    metadata <- loadMetaList files

loadMetaList :: [String] -> IO [Metadata]
loadMetaList file:files = do
    first <- loadMeta file
    rest <- loadMetaList files
    return (first:rest)

loadMeta :: String -> IO Metadata

如您所见,loadMetaList 不是惰性的。为了让它变得懒惰,它应该使用尾递归。类似于return (first:loadMetaList rest)

如何让 loadMetaList 变得懒惰

【问题讨论】:

  • 这是pipesconduit 的一个很好的用例。
  • 一开始我以为是“loadMetalList”,就像一堆重金属mp3一样
  • 其实就是为了加载mp3的元数据
  • 为什么不只拥有 loadMetaList :: [String] -> [IO Metadata] ?然后在需要时单独评估它们?
  • 这是一个实际实现的过度简化示例。 loadMetaList 可能会丢弃某些文件,具体取决于它们的元数据(例如流派)

标签: haskell file-io lazy-loading lazy-evaluation lazy-sequences


【解决方案1】:

IO monad 的 (&gt;&gt;=) 是这样的

loadMetaList :: [String] -> IO [Metadata]
loadMetaList file:files = do
    first <- loadMeta file
    rest <- loadMetaList files
    return (first:rest)

必须先运行操作loadMetaList files,然后才能执行return (first:rest)

你可以通过推迟loadMetaList files的执行来避免这种情况,

import System.IO.Unsafe

loadMetaList :: [String] -> IO [Metadata]
loadMetaList file:files = do
    first <- loadMeta file
    rest <- unsafeInterleaveIO $ loadMetaList files
    return (first:rest)

unsafeInterleaveIOfind 也使用)。这样,loadMetaList files 在需要其结果之前不会执行,如果您只需要 10 个文件的元数据,则只会加载那个。

它不像它的表亲unsafePerformIO那么不安全,但也应该小心处理。

【讨论】:

  • 请问 unsafeInterleaveIO 什么时候是“不安全的”?为什么在我的情况下它是安全的?
  • 在您的情况下不一定安全。但是由于find 已经使用了它,放入loadMetaList 并不会增加不安全性,所以这里不用太担心。 unsafeInterleaveIO 推迟其参数的执行,所以 a) 你失去了对事情完成顺序的可预测性,b) 文件可以在推迟执行时被更改,所以你可能会得到与你得到的结果不同的结果没有它,c) 它可能会使文件打开的时间比预期的要长,从而使您用完文件句柄,d) 我确定还有其他潜在问题。
  • 但是在 a) 不重要并且 b) 和 c) 已知不是问题的情况下,它可以大大简化很多事情。根据我有限的经验,大多数普通应用程序都属于该类别,但对于那些不属于该类别的应用程序来说,这可能是灾难性的。
【解决方案2】:

这是pipes 方式的操作方法。我真的不知道你是如何实现loadMetafind 的,所以我只是编造了一些东西:

import Pipes

find :: Producer FilePath IO ()
find = each ["heavy.mp3", "metal.mp3"]

type MetaData = String

loadMeta :: String -> IO MetaData
loadMeta file = return $ "This song is " ++ takeWhile (/= '.') file

loadMetaList :: Pipe FilePath MetaData IO r
loadMetaList = mapM loadMeta

要运行它,我们只需像管道一样组合处理阶段并使用runEffect 运行管道:

>>> runEffect $ find >-> loadMetaList >-> stdoutLn
This song is heavy
This song is metal

有几点需要指出:

  • 您可以将find 设为Producer,这样它也只会懒惰地搜索目录树。我知道您不需要此功能,因为您的文件集现在很小,但是以后当您的目录变大时很容易包含。

  • 它很懒,但没有unsafeInterleaveIO。它会立即生成每个输出,并且不会等待首先收集整个结果列表。

例如,即使我们使用无限的文件列表,它也可以工作:

>>> import qualified Pipes.Prelude as Pipes
>>> runEffect $ each (cycle ["heavy.mp3", "metal.mp3"]) >-> loadMetaList >-> Pipes.stdoutLn
This song is heavy
This song is metal
This song is heavy
This song is metal
This song is heavy
This song is metal
...
  • 它只会根据需要进行计算。如果我们指定只需要三个结果,即使我们提供了无限的文件列表,它也会执行返回两个结果所需的最小加载量。

例如,我们可以使用take 来限制结果的数量:

>>> runEffect $ each (cycle ["heavy.mp3", "metal.mp3"]) >-> loadMetaList >-> Pipes.take 3 >-> Pipes.stdoutLn
This song is heavy
This song is metal
This song is heavy

所以你问unsafeInterleaveIO 有什么问题。 unsafeInterleaveIO 的主要限制是您无法保证 IO 操作何时实际发生,这导致了以下常见陷阱:

  • Handles 在文件读取前被意外关闭

  • IO 动作迟到或从不发生

  • 纯代码有副作用,会抛出IOExceptions

Haskell 的IO 系统相对于其他语言的最大优势在于,Haskell 将评估模型与副作用的顺序完全解耦。当你使用惰性IO 时,你会失去这种解耦,然后副作用的顺序会与 Haskell 的评估模型紧密结合,这是一个巨大的倒退。

这就是为什么使用惰性IO 通常是不明智的,尤其是现在有简单而优雅的替代方法。

如果你想了解更多关于如何使用pipes 来安全地实现惰性IO,那么你可以阅读大量的pipes tutorial

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-02-01
    • 2013-01-21
    • 1970-01-01
    • 1970-01-01
    • 2018-04-17
    • 2016-04-26
    • 1970-01-01
    • 2023-04-03
    相关资源
    最近更新 更多