我是新手,monad让我完全糊涂了。给定一个文件名列表,我想知道是否所有文件都存在。
一般来说,我想这样做:
import System.Directory
allFilesPresent files = foldr (&&) True (map doesFileExist files)
但是我不知道这样做的正确方法是什么,因为这里涉及IO Bool
而不是Bool
。
帮助和解释会非常好,谢谢!
答案 0 :(得分:10)
您说得对,您的代码无效,因为map doesFileExist files
会返回IO Bool
而不是Bool
的列表。要解决此问题,您可以使用mapM
代替map
,这将为您提供IO [Bool]
。您可以使用>>=
块中的<-
或do
解压缩,然后在解压缩的foldr (&&)
和[Bool]
上使用return
。结果将是IO Bool
。像这样:
import System.Directory
allFilesPresent files = mapM doesFileExist files >>=
return . foldr (&&) True
或者使用do notation:
import System.Directory
allFilesPresent files = do bools <- mapM doesFileExist files
return $ foldr (&&) True bools
或使用liftM
中的Control.Monad
:
allFilesPresent files = liftM (foldr (&&) True) $ mapM doesFileExist files
或使用<$>
中的Control.Applicative
:
allFilesPresent files = foldr (&&) True <$> mapM doesFileExist files
答案 1 :(得分:6)
doesFileExist "foo.txt"
是IO Bool
,这意味着其结果取决于外部世界的状态。
您使用map doesFileExist files
走在正确的轨道上 - 此表达式将返回[IO Bool]
或世界相关表达式列表。实际需要的是包含bool列表的IO
表达式。您可以使用sequence
:
sequence :: Monad m => [m a] -> m [a]
或者,因为您只是使用序列/地图,mapM
辅助函数:
mapM :: Monad m => (a -> m b) -> [a] -> m [b]
mapM f xs = sequence (map f xs)
让我们回到你的代码。这是使用mapM
的版本,带有注释:
import System.Directory
-- When figuring out some unfamiliar libraries, I like to use type annotations
-- on all top-level definitions; this will help you think through how the types
-- match up, and catch errors faster.
allFilesPresent :: [String] -> IO Bool
-- Because allFilesPresent returns a computation, we can use do-notation to write
-- in a more imperative (vs declarative) style. This is sometimes easier for students
-- new to Haskell to understand.
allFilesPresent files = do
-- Run 'doesFileExist' tests in sequence, storing the results in the 'filesPresent'
-- variable. 'filesPresent' is of type [Bool]
filesPresent <- mapM doesFileExist files
-- The computation is complete; we can use the standard 'and' function to join the
-- list of bools into a single value.
return (and filesPresent)
替代版本使用更多声明性语法;这可能是一位经验丰富的Haskell程序员所写的:
allFilesPresent :: [String] -> IO Bool
allFilesPresent = fmap and . mapM doesFileExist
答案 2 :(得分:5)
请注意,如果您使用sequence
或mapM
,则选择不短路;即使其中一个文件不存在,您仍然检查其余文件是否存在。如果您想要短路,以下工作:
import System.Directory
andM :: Monad m => [m Bool] -> m Bool
andM [] =
return True
andM (m : ms) = do
b <- m
if b then
andM ms
else
return False
allFilesPresent :: [FilePath] -> IO Bool
allFilesPresent files = andM $ map doesFileExist files
或等效使用monad-loops package:
import System.Directory
import Control.Monad.Loops
allFilesPresent :: [FilePath] -> IO Bool
allFilesPresent = allM doesFileExist