Определите, существует ли список файлов в Haskell - PullRequest
5 голосов
/ 21 октября 2010

Я новичок, и монады меня совершенно смущают.Учитывая список имен файлов, я хотел бы знать, существуют ли все файлы.

Как правило, я хотел бы сделать:

import System.Directory
allFilesPresent files = foldr (&&) True (map doesFileExist files)

Однако я не знаю, как это правильночтобы сделать это, потому что здесь задействовано IO Bool вместо * 1007. *

Помощь и объяснение были бы очень хорошими, спасибо!

Ответы [ 3 ]

11 голосов
/ 21 октября 2010

Вы правы, ваш код не работает, потому что map doesFileExist files возвращает список IO Bool s вместо Bool.Чтобы исправить это, вы можете использовать mapM вместо map, что даст вам IO [Bool].Вы можете распаковать его, используя >>= или <- внутри do -блока, а затем использовать foldr (&&) на распакованных [Bool] и return та.Результатом будет IO Bool.Например:

import System.Directory
allFilesPresent files = mapM doesFileExist files >>=
                        return . foldr (&&) True

Или с помощью записи do:

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
6 голосов
/ 21 октября 2010

doesFileExist "foo.txt" - это IO Bool, что означает, что его результат зависит от состояния внешнего мира.

Вы на правильном пути с map doesFileExist files - это выражение вернет [IO Bool], или список зависимых от мира выражений. На самом деле нужно выражение IO, содержащее список bools. Вы можете получить это используя 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
5 голосов
/ 21 октября 2010

Обратите внимание, что если вы используете 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

Или эквивалентно с помощью пакета монад-петель :

import System.Directory
import Control.Monad.Loops

allFilesPresent :: [FilePath] -> IO Bool
allFilesPresent = allM doesFileExist
...