I was trying to find a directory name by appending a number until I found a name that doesn't yet exist:
head <$> filterM (fmap not . fexists_) [ getDatedDir t d n | n <- [0..] ]
The problem with this is that it never returns. I think the problem is that although IO is a Functor, filterM has to do all the IO before the head effects; that is, it has to evaluate fexists for every n - and that's of course infinite.
Now, I can solve this:
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)
But I feel that there ought to be a more elegant approach, using filterM or something similar.
This feels like a common enough pattern that there is probably an extisting function in Control.Monad, I'm just not seeing it.
There's firstM
from 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.
An equivalent without monads, would be find :: Foldable t => (a -> Bool) -> t a -> Maybe a
. But I can not find a version to let it work with monads.
An idea might be to implement findM
ourselves:
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))
You can then use it like:
import System.Directory(doesFileExist)
main = do
Just fln <- findM (not . doesDirectoryExist) (map (getDatedDir t d) [0..])
putStrLn fln
to print the first filename such that the file does not exists. Of course you can also process fln
in a different way.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With