Usaré tres trucos para resolver su pregunta.
- Trick 1: Utilice la biblioteca
pipes
para transmitir los nombres de archivos concurrentes con recorrer el árbol.
- Truco 2: Utilice el transformador
StateT (Seq FilePath)
para lograr un recorrido transversal en anchura.
- Truco 3: Utilice el transformador
MaybeT
para evitar la recursión manual al escribir el bucle y salir.
El siguiente código combina estos tres trucos en una sola pila de transformadores monad.
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)
Esto crea un generador de nombres de archivo en amplitud que pueden ser consumidos concurrente con el recorrido de árbol. Se consumen los valores usando:
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>
incluso se puede optar por no exigir todos los valores:
-- 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>
Más importante aún, ese último ejemplo sólo recorrer el árbol tanto como sea necesario para generar los tres archivos y luego se detendrá. ¡Esto evita recorrer innecesariamente todo el árbol cuando todo lo que quería eran 3 resultados!
Para obtener más información sobre el truco de la biblioteca pipes
, consulte pipes tutorial en Control.Pipes.Tutorial
.
Para obtener más información sobre el truco de bucle, lea este blog post.
No pude encontrar un buen enlace para el truco de la cola para el primer cruce transversal, pero sé que está por ahí en alguna parte. Si alguien más conoce un buen enlace para esto, solo edita mi respuesta para agregarlo.
gracias por su código. es una gran ayuda para entender las tuberías. Estaba leyendo sobre conductos y estaba planeando usarlo, pero esperaba que primero debería tener una solución simple y perezosa solo para el cruce de árboles. lo probé y funciona, pero no se repite en el árbol y no entiendo dónde recurriría en tu código. el código faltante está filtrando "." y ".." de la lista de directorios getUsefulContents path = do names <- getDirectoryContents ruta return (filtro ('notElem' [". "," .. "]) nombres) – user855443
en una inspección más profunda veo el (recursión oculta) en la última línea con el estado de elevación, donde los nuevos nombres de archivo se agregan a la lista "todo". no vi esto, porque el código no produce el camino de archivo completo para los archivos agregados. el valor de la ruta es el valor inicial original y no se establece cada vez para el nombre del archivo actual -> reemplazar ruta con archivo, luego funciona. para funcionar por completo, uno tiene que comprobar los permisos en el directorio, que hago con getInfo :: FilePath -> IO Info que tomé del mundo real Haskell capítulo 9. – user855443
se encuentra con dificultades, cuando se encuentra con enlaces y yo tiene que agregar una prueba para filtrar también los enlaces. ¡funciona y utiliza todos mis 4 núcleos! todavía hay una pérdida de memoria, ya que el uso crece muy lentamente hasta que se agota la memoria. ¿Puedes ver dónde? ¡su ayuda es muy apreciada, era exactamente lo que necesitaba para tener un buen ejemplo práctico de cómo usar tuberías al atravesar un árbol! – user855443