上周,用户 Masse 提出了一个关于在 Haskell 目录中递归列出文件的问题。我的第一个想法是尝试使用List
包中的单子列表,以避免在打印开始之前在内存中构建整个列表。我实现了如下:
module Main where
import Prelude hiding (filter)
import Control.Applicative ((<$>))
import Control.Monad (join)
import Control.Monad.IO.Class (liftIO)
import Control.Monad.ListT (ListT)
import Data.List.Class (cons, execute, filter, fromList, mapL)
import System (getArgs)
import System.Directory (getDirectoryContents, doesDirectoryExist)
import System.FilePath ((</>))
main = execute . mapL putStrLn . listFiles =<< head <$> getArgs
listFiles :: FilePath -> ListT IO FilePath
listFiles path = liftIO (doesDirectoryExist path) >>= listIfDir
where
valid "." = False
valid ".." = False
valid _ = True
listIfDir False = return path
listIfDir True
= cons path
$ join
$ listFiles
<$> (path </>)
<$> (filter valid =<< fromList <$> liftIO (getDirectoryContents path))
这很好用,因为它立即开始打印并且使用很少的内存。FilePath -> IO [FilePath]
不幸的是,它也比同类版本慢了几十倍。
我究竟做错了什么?我从来没有在这样的玩具示例之外使用过这个List
包ListT
,所以我不知道会有什么样的性能,但是 30 秒(相对于几分之一秒)来处理一个包含 ~40,000 个文件的目录似乎太多了减缓。