1

我以传统方式递归地遍历目录。这是一个工作原型:

traverseFlatDst :: FilePath -> Int -> Int -> FilePath -> IO ()
traverseFlatDst dstRoot total totw srcDir = do
  (dirs, files) <- listDir srcDir
  mapM_ (\file -> putStrLn (printf "%s" (strp file))) files    -- tracing
  let traverse = traverseFlatDst dstRoot total totw
  mapM_ traverse dirs

我有一个不那么不寻常的要求:每个跟踪行都应该编号(它不是真正用于跟踪)。像这样:

traverseFlatDst :: FilePath -> Int -> Int -> FilePath -> IO ()
traverseFlatDst dstRoot total totw srcDir = do
  (dirs, files) <- listDir srcDir
  mapM_ (\file -> putStrLn (printf "%d: %s" counterFromNowhere (strp file))) files
  let traverse = traverseFlatDst dstRoot total totw
  mapM_ traverse dirs

到目前为止,我看到的所有解决方案都丑陋得超出想象,如果适用的话。有没有很好的管理方法?

4

5 回答 5

3

您可以通过向函数添加附加效果来实现此目的;即状态效应。

import Control.Monad.State

printPath :: (PrintfArg t, Show a) => (t, a) -> IO ()
printPath (l, file) = printf "%d : %s\n" l (show file)

traverseFlatDst :: Path Abs Dir -> IO ()
traverseFlatDst =
  let loop srcDir = do
        (dirs, files) <- listDir srcDir
        i <- get
        put (i + length files)
        mapM_ (liftIO . printPath) $ zip [i..] files
        mapM_ loop dirs
  in \s -> evalStateT (loop s) 0

(注意:为了清楚起见,我还删除了未使用的参数)。

但是,我不建议以这种方式编写此函数。从语义上讲,您的函数正在收集一堆文件路径。您应该从函数中返回它们,而不是打印它们;您以后可以随时打印它们!修改版的逻辑其实相当简单:

traverseFlatDst' :: Path Abs Dir -> IO [Path Abs File]
traverseFlatDst' srcDir = do
  (dirs, files) <- listDir srcDir
  (concat . (files:)) <$> mapM traverseFlatDst' dirs

您可以使用此功能打印带有数字的文件,而无需明确跟踪某些状态,因为您可以“一次”访问所有文件:

> traverseFlatDst' somePath >>= mapM_ printPath . zip [0..]

另请注意,第二版比第一版严格得多;在开始打印任何内容之前,它将遍历整个目录树。作为一般规则,无论如何,严格的版本更好,但如果你想要懒惰的版本,你可以这样写unsafeInterleaveIO

import System.IO.Unsafe (unsafeInterleaveIO)

traverseFlatDst' :: Path Abs Dir -> IO [Path Abs File]
traverseFlatDst' srcDir = do
  (dirs, files) <- listDir srcDir
  files' <- unsafeInterleaveIO $ mapM traverseFlatDst' dirs
  return $ concat $ files:files'
于 2017-12-11T17:52:11.080 回答
3

我可能会使用流媒体库之类的流媒体将枚举文件与添加数字和打印装饰条目分开:

import Streaming
import qualified Streaming.Prelude as S

traverseFlatDst :: FilePath -> Int -> Int -> FilePath -> Stream (Of FilePath) IO ()
traverseFlatDst dstRoot total totw srcDir = do
  (dirs, files) <- liftIO $ listDir srcDir
  S.each files
  mapM_ (traverseFlatDst dstRoot total totw) dirs

decorate :: Stream (Of FilePath) IO r -> Stream (Of (Int,FilePath)) IO r
decorate stream = S.zip (S.enumFrom 1) stream

display:: Stream (Of (Int,FilePath)) IO () -> IO ()
display = S.mapM_ $ \(index,path) ->  putStrLn $ show index ++ " " ++ path

其中S.each, S.zip,S.mapM_来自流媒体

于 2017-12-11T19:09:27.890 回答
0

不知从何而来?当然不是。

不过,你可以用数字压缩你files的数字,然后再mapM覆盖它们:

mapM_ (\(file, counter) -> putStrLn (printf "%d: %s" counter (strp file))) (zip [0..] files)
于 2017-12-11T17:00:14.600 回答
0

最终解决方案,借用How to implement a global counter using Monad?

import Data.IORef

type Counter = Int -> IO Int

makeCounter :: IO Counter
makeCounter = do
  r <- newIORef 0
  return (\i -> do modifyIORef r (+i)
                   readIORef r)


printPath :: Counter -> FilePath -> IO ()
printPath counter file = do
  n <- counter 1
  putStrLn (printf "%d : %s" n (strp file))


traverseFlatDst :: FilePath -> Int -> Int -> Counter -> FilePath -> IO ()
traverseFlatDst dstRoot total totw cnt srcDir = do
  (dirs, files) <- listDir srcDir
  let iterate = printPath cnt
  mapM_  iterate files                -- tracing
  let traverse = traverseFlatDst dstRoot total totw cnt
  mapM_ traverse dirs


groom :: FilePath -> FilePath -> Int -> IO ()
groom src dst total = do
  counter <- makeCounter
  let totWidth = length $ show total
  traverseFlatDst dst total totWidth counter src
  putStrLn (printf "total: %d, width: %d" total totWidth)

仍然被关在笼子里,不能在任何地方使用,但没关系。不丑。

于 2017-12-11T23:59:22.430 回答
0

该解决方案不需要额外的库,在找到每个文件时就对其进行处理,并且为了分离关注点,不需要traverseFlatDst知道对生成的文件做了什么。

最后一个特性是通过将一个小的有效状态机(实际上是一个步进函数)作为参数传递给traverseFlatDst,并traverseFlatDst在机器状态中进行多态化来实现的,因此它对此一无所知:

{-# language RankNTypes #-}
import Control.Monad (foldM)

type Source e = forall s. (s -> e -> IO s) -> s -> IO s

traverseFlatDst :: FilePath -> Int -> Int -> FilePath -> Source FilePath
traverseFlatDst dstRoot total totw srcDir step state = do
  (dirs, files) <- listDir srcDir
  state' <- foldM step state files
  foldM (\s path -> traverseFlatDst dstRoot total totw path step s) state' dirs

-- Pass this as the step argument to traverseFlatDst
-- The counter is the state.
step :: Int -> FilePath -> IO Int
step index path = do
    putStrLn $ show index ++ " " ++ path
    return $ succ index
于 2017-12-12T18:50:41.753 回答