我写了一个Haskell模块,按宽度优先顺序列出目录的所有内容。以下是源代码。
module DirElements (dirElem) where
import System.Directory (getDirectoryContents, doesDirectoryExist)
import System.FilePath ((</>))
dirElem :: FilePath -> IO [[FilePath]]
dirElem dirPath = iterateM (not.null) (concatMapM getDirectoryContents') [dirPath] >>= return.tail
getDirectoryContents' :: FilePath -> IO [FilePath]
getDirectoryContents' dirPath = do
isDir <- do doesDirectoryExist dirPath
if isDir then dirContent else return [] where
dirContent = do
contents <- getDirectoryContents dirPath
return.(map (dirPath</>)).tail.tail $ contents
iterateM :: (Monad m) => (a -> Bool) -> (a -> m a) -> a -> m [a]
iterateM fb f x = do --Notice: Due to the the implementation of >>=, iterateM can't be writen like iterate which gives a infinite list and have type of iterateM :: (Monad m) => (a -> Bool) -> (a -> m a) -> a -> m [a]
if fb x
then do
tail <- do {fx <- f x; iterateM fb f fx}
return (x:tail)
else return []
concatMapM :: Monad m => (a -> m[b]) -> [a] -> m[b]
concatMapM f list = mapM f list >>= return.concat
它工作正常但是在大型目录上执行时,它会暂停“暂停”一段时间,然后弹出所有结果。
经过研究后,我发现与sequence $ map return [1..]::[[Int]]
相同的问题请参见Why the Haskell sequence function can't be lazy or why recursive monadic functions can't be lazy
答案 0 :(得分:12)
每隔一段时间就出现一次,答案最终是使用类似库的迭代。最近经常建议的是Proxy库。
之前我曾见过Conduit解决方案和一些优雅的monadic解决方案,但我现在还没有找到它们。
答案 1 :(得分:6)
我修改了Davorak链接到的旧答案,以使用新的pipes
库。
它使用StateP
来保留未遍历目录的队列,以便它可以进行广度优先遍历。为方便起见,它使用MaybeP
退出循环。
import Control.Monad
import Control.Proxy
import Control.Proxy.Trans.Maybe
import Control.Proxy.Trans.State as S
import Data.Sequence hiding (filter)
import System.FilePath.Posix
import System.Directory
getUsefulContents :: FilePath -> IO [FilePath]
getUsefulContents path
= fmap (filter (`notElem` [".", ".."])) $ getDirectoryContents path
traverseTree
:: (Proxy p)
=> FilePath
-> () -> Producer (MaybeP (StateP (Seq FilePath) p)) FilePath IO r
traverseTree path () = do
liftP $ S.modify (|> path)
forever $ do
x <- liftP $ S.gets viewl
case x of
EmptyL -> mzero
file :< s -> do
liftP $ S.put s
respond file
p <- lift $ doesDirectoryExist file
when p $ do
names <- lift $ getUsefulContents file
let namesfull = map (file </>) names
liftP $ forM_ namesfull $ \name ->
S.modify (|> name)
这定义了一个广度优先的懒惰文件生成器。如果将它连接到打印阶段,它将在遍历树时打印出文件:
main = runProxy $ evalStateK empty $ runMaybeK $
traverseTree "/tmp" >-> putStrLnD
懒惰意味着如果你只需要3个文件,它只会根据需要遍历树来生成三个文件,然后就会停止:
main = runProxy $ evalStateK empty $ runMaybeK $
traverseTree "/tmp" >-> takeB_ 3 >-> putStrLnD
如果您想详细了解pipes
library,我建议您阅读the tutorial。
答案 2 :(得分:3)
每个人都在告诉你使用迭代或管道等,这是当前流行的方法。但是还有另一种经典的方法来做到这一点!只需使用unsafeInterleaveIO
中的System.IO.Unsafe
即可。 IO a -> IO a
类型的所有这些函数都会修改IO操作,以便它只在需要值thunk时才实际执行IO,这正是您所要求的。您可以使用它来简单地用您想要的语义编写iterateM
。
这样的例子是unsafeInterleaveIO
闪耀的地方。
但是,我确信,请注意名称中的“不安全” - 还有其他示例,您希望直接控制文件句柄和资源使用等,unsafeInterleaveIO
确实会是坏消息,甚至可能引入违反参考透明度的行为。
(有关更多讨论,请参阅此答案:When is unsafeInterleaveIO unsafe?)
但同样,在这样的情况下,我认为unsafeInterleaveIO
是明显,正确和直接的结果。