私はあなたの質問を解決するために3つの別々のトリックを使用します。
- 秘訣1:
pipes
ライブラリを使用して、ツリーのトラバースと同時にファイル名をストリーミングします。
- 秘訣2:
StateT (Seq FilePath)
トランスフォーマーを使用して幅優先探索を実行します。
- 秘訣3:
MaybeT
ループを書き込んで終了するときに手動で再帰しないように、トランスフォーマーを使用します。
次のコードは、これら3つのトリックを1つのモナド変換子スタックに組み合わせたものです。
import Control.Monad
import Control.Monad.Trans
import Control.Monad.Trans.Maybe
import Control.Monad.State.Lazy
import Control.Pipe
import Data.Sequence
import System.FilePath.Posix
import System.Directory
loop :: (Monad m) => MaybeT m a -> m ()
loop = liftM (maybe () id) . runMaybeT . forever
quit :: (Monad m) => MaybeT m a
quit = mzero
getUsefulContents :: FilePath -> IO [FilePath]
getUsefulContents path
= fmap (filter (`notElem` [".", ".."])) $ getDirectoryContents path
permissible :: FilePath -> IO Bool
permissible file
= fmap (\p -> readable p && searchable p) $ getPermissions file
traverseTree :: FilePath -> Producer FilePath IO ()
traverseTree path = (`evalStateT` empty) $ loop $ do
-- All code past this point uses the following monad transformer stack:
-- MaybeT (StateT (Seq FilePath) (Producer FilePath IO)) ()
let liftState = lift
liftPipe = lift . lift
liftIO = lift . lift . lift
liftState $ modify (|> path)
forever $ do
x <- liftState $ gets viewl
case x of
EmptyL -> quit
file :< s -> do
liftState $ put s
liftPipe $ yield file
p <- liftIO $ doesDirectoryExist file
when p $ do
names <- liftIO $ getUsefulContents file
-- allowedNames <- filterM permissible names
let namesfull = map (path </>) names
liftState $ forM_ namesfull $ \name -> modify (|> name)
これにより、ツリートラバーサルと同時に使用できる幅優先ファイル名のジェネレーターが作成されます。次を使用して値を消費します。
printer :: (Show a) => Consumer a IO r
printer = forever $ do
a <- await
lift $ print a
>>> runPipe $ printer <+< traverseTree path
<Prints file names as it traverses the tree>
すべての値を要求しないことを選択することもできます。
-- Demand only 'n' elements
take' :: (Monad m) => Int -> Pipe a a m ()
take' n = replicateM_ n $ do
a <- await
yield a
>> runPipe $ printer <+< take' 3 <+< traverseTree path
<Prints only three files>
さらに重要なことに、この最後の例では、3つのファイルを生成するために必要なだけツリーをトラバースし、その後停止します。これにより、必要な結果が3つだけの場合に、ツリー全体を無駄にトラバースすることを防ぎます。
pipes
ライブラリのトリックの詳細については、のパイプチュートリアルを参照してくださいControl.Pipes.Tutorial
。
ループトリックの詳細については、このブログ投稿をお読みください。
幅優先探索のキュートリックの適切なリンクは見つかりませんでしたが、どこかにあることはわかっています。他の誰かがこれに適したリンクを知っている場合は、私の答えを編集して追加してください。