我以传统的方式递归地走一个目录。这是一个工作原型:
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
到目前为止,我所见过的所有解决方案都难以想象,如果适用的话。有没有一种很好的方法来管理它?
答案 0 :(得分: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
您可以使用此功能打印带有数字的文件,而无需明确跟踪某些状态,因为您可以立即访问所有文件&#39;:
> 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'
答案 1 :(得分:3)
我可能会使用像streaming这样的流媒体库来分隔枚举文件,不添加数字和打印装饰条目:
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
答案 2 :(得分:0)
从哪儿来?当然不是。
您可以使用数字压缩files
,然后mapM
对其进行压缩:
mapM_ (\(file, counter) -> putStrLn (printf "%d: %s" counter (strp file))) (zip [0..] files)
答案 3 :(得分: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)
仍然关在笼子里,不能在任何地方使用,但没关系。不难看。
答案 4 :(得分:0)
此解决方案不需要额外的库,在找到每个文件时处理它们,并且为了分离关注点,不需要traverseFlatDst
知道对生成的文件执行了什么操作。< / p>
最后一个功能是通过将一个小型有效状态机(真正的一个步骤函数)作为参数传递给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