Что такое идиоматический способ Haskell для работы с предикатами в IO?

Для какой-то операции с файлом мне нужно проверить, существует ли файл, был ли он изменен, и только потом выполнять над ним какую-то операцию. Мой код новичка на Haskell выглядит следующим образом (упрощенно):

someFileOp ::FileContents -> FilePath -> IO (FileOpResult)
someFileOp contents absFilePath = do
    fileExists <- DIR.doesFileExist absFilePath
    if fileExists
        then do
            isMod <- isModified contents absFilePath
            if isMod
                then return FileModified
            else return $ doSomethingWithFile
        else return FileNotFound

Это работает. Однако вложенные выражения if выглядят для меня неправильными - не похожими на FP. Какой идиоматический способ проверить несколько логических условий в IO, а затем предпринять какие-либо действия в зависимости от их результата?


person Ulrich Schuster    schedule 29.05.2020    source источник
comment
На самом деле это не специфично для Haskell, но: я бы не стал заморачиваться с doesFileExist. Вместо этого поймайте исключение, которое isModified выдает, когда оно не существует. В противном случае у вас есть состояние гонки.   -  person Daniel Wagner    schedule 29.05.2020
comment
Это достойное функциональное программирование, и, безусловно, легкое для понимания, что, на мой взгляд, является его качеством. Если вы хотите связать это, вы можете найти инфиксный оператор, который оценивает RHS, когда LHS IO возвращает True. Кроме того, может помочь монада Либо.   -  person Paul R    schedule 29.05.2020


Ответы (3)


Код, который вы разместили, выглядит хорошо для меня. Другая возможность состоит в том, чтобы действовать в короткозамыкающей монаде, такой как ExceptT Err IO.

data Err = FileNotFound | FileModified

getFileContents :: FilePath -> ExceptT Err IO FileContents
getFileContents fp = do
    exists <- doesFileExist fp
    if exists then {- ... -} else throwError FileNotFound

someFileOp :: FileContents -> FilePath -> ExceptT Err IO FileOpResult
someFileOp fc fp = do
    fc' <- getFileContents fp
    when (fc /= fc') (throwError FileModified)
    doSomethingWithFile
person Daniel Wagner    schedule 29.05.2020

Игнорируя хорошее замечание Даниэля о гонках и почему проверка файлов часто просто не выполняется, тем более решение Haskell обычно является монадным преобразователем. Это типичный случай, когда преобразователь ExceptT имеет смысл. Я также включил (неправильное) использование ContT на случай, если вам интересно и вы хотите изучить:

import System.Directory as DIR
import Control.Monad.Trans.Cont
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Trans.Except

isModified :: a -> b -> IO Bool
isModified _ _ = pure False

type FileOpResult = Either String String

someFileOp_cont :: String -> FilePath -> IO FileOpResult
someFileOp_cont contents absFilePath = evalContT $ callCC $ \exit -> do
    fileExists <- liftIO $ DIR.doesFileExist absFilePath
    unless fileExists (exit (Left "FileNotFound"))
    isMod <- liftIO $ isModified contents absFilePath
    when isMod (exit (Left "FileModified"))
    return (Right "doSomethingWithFile")

someFileOp_except :: String -> FilePath -> IO FileOpResult
someFileOp_except contents absFilePath = runExceptT $ do
    fileExists <- liftIO $ DIR.doesFileExist absFilePath
    unless fileExists (throwE "FileNotFound")
    isMod <- liftIO $ isModified contents absFilePath
    when isMod (throwE "FileModified")
    return "doSomethingWithFile"
person Thomas M. DuBuisson    schedule 29.05.2020
comment
Действительно ли это больше неправильное использование ContT, чем любое другое использование? :) - person chepner; 29.05.2020

Я бы использовал whenM :: Monad m => m Bool -> m () -> m() или ifM :: Monad m => m Bool -> m a -> m a -> m a, доступные, например, в extra:

-- | Like 'when', but where the test can be monadic.
whenM :: Monad m => m Bool -> m () -> m ()
whenM mb mt = mb >>= \b ->
  if b
    then mt
    else return ()

-- | Like @if@, but where the test can be monadic.
ifM :: Monad m => m Bool -> m a -> m a -> m a
ifM mb mt me = mb >>= \b ->
  if b
    then mt
    else me
person Sebastian Graf    schedule 29.05.2020