Каким идиоматическим способом c Haskell воздействовать на предикаты в IO? - PullRequest
4 голосов
/ 29 мая 2020

Для некоторой операции с файлом мне нужно проверить, существует ли файл, был ли он изменен, и только затем выполнить какую-то операцию над ним. Мой код newb ie 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-подобными. Каким был бы идиоматический c способ проверить несколько логических условий в вводе-выводе и затем предпринять некоторые действия в зависимости от их результата?

Ответы [ 3 ]

5 голосов
/ 29 мая 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
5 голосов
/ 29 мая 2020

Игнорируя хорошее замечание Даниэля о гонках и почему проверка файлов часто просто не выполняется, решение more 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"
4 голосов
/ 29 мая 2020

Код, который вы разместили, мне нравится. Другая возможность - действовать в короткозамыкающей монаде, например, 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...