从非IO列表创建惰性IO列表

And*_*rey 4 file-io haskell lazy-loading lazy-evaluation lazy-sequences

我有一个由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
Run Code Online (Sandbox Code Playgroud)

如您所见,loadMetaList 不是懒惰的.因为它是懒惰的,它应该使用尾递归.有点像return (first:loadMetaList rest).

如何使loadMetaList变得懒惰

Dan*_*her 9

(>>=)该的IO单子是这样的,在

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

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)
Run Code Online (Sandbox Code Playgroud)

unsafeInterleaveIO(find也使用).这样,loadMetaList files直到需要它才会执行,如果只需要10个文件的元数据,那么只会加载它.

它并不像它的堂兄那样不安全unsafePerformIO,但也应该小心处理.

  • 在您的情况下,它不一定安全.但是因为`find`已经使用它,所以放入`loadMetaList`不会增加不安全性,所以这里不用担心太多.`unsafeInterleaveIO`推迟执行它的参数,所以a)你失去了事情完成顺序的可预测性,b)文件可以在执行被推迟时被改变,所以你可能会得到与你得到的结果不同的结果没有它,c)它可能使文件打开时间超过预期,从而使你用完文件句柄,d)我确定还有其他潜在的问题. (3认同)

Gab*_*lez 8

这是你如何做到这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
Run Code Online (Sandbox Code Playgroud)

要运行它,我们只需构建像管道一样的处理阶段并使用runEffect以下命令运行管道:

>>> runEffect $ find >-> loadMetaList >-> stdoutLn
This song is heavy
This song is metal
Run Code Online (Sandbox Code Playgroud)

有几点需要指出:

  • 你可以制作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
...
Run Code Online (Sandbox Code Playgroud)
  • 它只会根据需要进行计算.如果我们指定我们只需要三个结果,它将执行返回两个结果所需的最小加载量,即使我们提供了无限的文件列表.

例如,我们可以使用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
Run Code Online (Sandbox Code Playgroud)

所以你问到了什么问题unsafeInterleaveIO.主要限制unsafeInterleaveIO是您无法保证IO实际发生的行为何时会导致以下常见陷阱:

  • Handle在读取文件之前意外关闭了

  • IO 行动迟到或永远不会发生

  • 纯代码有副作用和投掷IOExceptions

Haskell IO系统相对于其他语言的最大优点是Haskell完全将评估模型与副作用的顺序分离.当你使用懒惰时IO,你会失去那种脱钩,然后副作用的顺序变得与Haskell的评估模型紧密结合,这是一个巨大的倒退.

这就是为什么使用懒惰一般都不明智IO,特别是现在有了简单而优雅的替代方案.

如果您想了解更多关于如何安全地pipes实现懒惰的信息IO,那么您可以阅读大量的管道教程.