如何在无限列表中使用 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.
我试图通过附加数字来查找目录名称,直到找到一个尚不存在的名称:
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.