如何在无限列表中使用 filterM

How to use filterM with an infinite list

我试图通过附加数字来查找目录名称,直到找到一个尚不存在的名称:

head <$> filterM (fmap not . fexists_) [ getDatedDir t d n | n <- [0..] ]

问题是它从来没有 returns。我认为问题在于,尽管 IO 是一个 Functor,但 filterM 必须在 head effects 之前完成所有 IO;也就是说,它必须为每个 n 评估 fexists - 这当然是无限的。

现在,我可以解决这个问题:

go t d 0
where go t d n = do
    let dir = getDatedDir t d n
    fexists_ dir >>= \case
        False -> return dir
        True  -> go t d (n+1)

但我觉得应该有更优雅的方法,使用 filterM 或类似的东西。

这感觉很常见,Control.Monad 中可能存在一个函数,我只是没看到它。

没有 monad 的等价物是 find :: Foldable t => (a -> Bool) -> t a -> Maybe a。但是我找不到让它与 monads 一起工作的版本。

一个想法可能是 findM 我们自己实施:

import Data.Bool(bool)

findM :: Monad m => (a -> m Bool) -> [a] -> m (Maybe a)
findM f [] = return Nothing
findM f (x:xs) = f x >>= bool (findM f xs) (return (Just x))

然后您可以像这样使用它:

import System.Directory(doesFileExist)

main = do
    Just fln <- findM (not . doesDirectoryExist) (map (getDatedDir t d) [0..])
    putStrLn fln

打印第一个文件名,使文件不存在。当然你也可以用不同的方式处理fln

firstM 来自 Control.Monad.Loops:

firstM :: Monad m => (a -> m Bool) -> [a] -> m (Maybe a)

return the first value from a list, if any, satisfying the given predicate.