4

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

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 变得懒惰

4

2 回答 2

9

单子的是这样(>>=)IO

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)

with unsafeInterleaveIOfind也使用)。这样,loadMetaList files直到需要它的结果才会执行,如果您只需要 10 个文件的元数据,则只会加载那个。

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

于 2013-04-26T19:32:45.707 回答
8

这是你如何做到这一点的pipes方式。我真的不知道你是如何实现loadMetaandfind的,所以我只是编造了一些东西:

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

有几个关键点需要指出:

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

  • 它很懒,但没有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 将评估模型与副作用的顺序完全解耦。当你使用 lazyIO时,你失去了这种解耦,然后副作用的顺序与 Haskell 的评估模型紧密结合,这是一个巨大的倒退。

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

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

于 2013-04-26T21:59:23.817 回答