在Haskell中流式传输目录的递归下降

Ral*_*lph 11 haskell directory-structure lazy-evaluation

我正在尝试使用Haskell进行目录结构的递归下降.我想只根据需要检索子目录和文件(懒惰).

我编写了以下代码,但是当我运行它时,跟踪显示在第一个文件之前访问了所有目录:

module Main where

import Control.Monad ( forM, forM_, liftM )
import Debug.Trace ( trace )
import System.Directory ( doesDirectoryExist, getDirectoryContents )
import System.Environment ( getArgs )
import System.FilePath ( (</>) )

-- From Real World Haskell, p. 214
getRecursiveContents :: FilePath -> IO [FilePath]
getRecursiveContents topPath = do
  names <- getDirectoryContents topPath
  let
    properNames =
      filter (`notElem` [".", ".."]) $
      trace ("Processing " ++ topPath) names
  paths <- forM properNames $ \name -> do
    let path = topPath </> name
    isDirectory <- doesDirectoryExist path
    if isDirectory
      then getRecursiveContents path
      else return [path]
  return (concat paths)

main :: IO ()
main = do
  [path] <- getArgs
  files <- getRecursiveContents path
  forM_ files $ \file -> putStrLn $ "Found file " ++ file
Run Code Online (Sandbox Code Playgroud)

如何将文件处理与下降交错?是,这个问题files <- getRecursiveContents path的行动得到了如下之前执行forM_main

Gab*_*lez 9

这正是iteratees/coroutines旨在解决的问题.

您可以轻松地执行此操作pipes.我对你所做的唯一变化getRecursiveContents是,使之成为ProducerFilePathS和对respond返回它的文件名来代替.这使得下游可以立即处理文件名,而不是等待getRecursiveContents完成.

module Main where

import Control.Monad ( forM_, liftM )
import Control.Proxy
import System.Directory ( doesDirectoryExist, getDirectoryContents )
import System.Environment ( getArgs )
import System.FilePath ( (</>) )

getRecursiveContents :: (Proxy p) => FilePath -> () -> Producer p FilePath IO ()
getRecursiveContents topPath () = runIdentityP $ do
  names <- lift $ getDirectoryContents topPath
  let properNames = filter (`notElem` [".", ".."]) names
  forM_ properNames $ \name -> do
    let path = topPath </> name
    isDirectory <- lift $ doesDirectoryExist path
    if isDirectory
      then getRecursiveContents path ()
      else respond path

main :: IO ()
main = do
    [path] <- getArgs
    runProxy $
            getRecursiveContents path
        >-> useD (\file -> putStrLn $ "Found file " ++ file)
Run Code Online (Sandbox Code Playgroud)

这会在遍历树时立即打印出每个文件,并且不需要延迟IO.使用文件名更改操作也非常容易,因为您只需useD使用实际的文件处理逻辑切换出舞台.

要了解更多信息pipes,我强烈建议您阅读Control.Proxy.Tutorial.

  • 我更新了Pipes 4的当前API而不是Pipes 3的代码,但是这里粘贴的时间太长了,所以我对它进行了强化:https://gist.github.com/FranklinChen/133cb61af931a08bbe20 (3认同)

Pet*_*lák 7

使用懒惰IO/unsafe...不是去的好办法.懒惰IO导致许多问题,包括未封闭的资源和在纯代码中执行不纯的操作.(另请参阅Haskell Wiki上的惰性I/O问题.)

一种安全的方法是使用一些iteratee/enumerator库.(替换有问题的懒惰IO是开发这些概念的动机.)您getRecursiveContents将成为数据源(AKA枚举器).并且一些迭代器将使用这些数据.(另请参阅Haskell wiki上的Enumerator和iteratee.)

枚举器一个教程,它给出了遍历和过滤目录树的示例,实现了一个简单的查找实用程序.它实现了方法

enumDir :: FilePath -> Enumerator FilePath IO b
Run Code Online (Sandbox Code Playgroud)

这基本上就是你需要的.我相信你会发现它很有趣.

也有一个很好的文章,解释在iteratees 单子读者,第16:Iteratee:教老折新把戏由约翰·W·拉托时,笔者iteratee库.

今天,许多人更喜欢新的图书馆,如管道.您可能对比较感兴趣:Enumerators vs. Conduits vs. Pipes的优缺点是什么?.