A "first n files that don't exist" function could be something like:
nexist :: Int -> [FilePath] -> IO [FilePath]
nexist n l = take n . map fst . filter (not.snd) . zip l <$> mapM doesFileExist l
A "first n files that don't exist" function could be something like:
nexist :: Int -> [FilePath] -> IO [FilePath]
nexist n l = take n . map fst . filter (not.snd) . zip l <$> mapM doesFileExist l
We don't write code primarily for the compiler, if we would then we wouldn't be having this discussion because the compiler doesn't mind spaghetti. We write code for the next guy who will try to read, understand and modify the code. As an experienced programmer I can tell you, but you probably know anyway, that in all languages you can write very concise but completely incomprehensible snippets which even you won't understand in 2 weeks.
filterM' f xs = map fst . filter snd . zip xs <$> mapM f xs
So, given filterM, the code would have looked like nexist n xs = take n <$> filterM (fmap not . doesFileExist) xs
Which doesn't look clever at all and it's fairly easy to understand.By the way, I like the (fmap not . doesFileExist) bit. It´s cleaner and less cluttered than (\f -> doesFileExist f >>= return . not)