在 IO 中对谓词进行操作的惯用 Haskell 方式是什么?
What is the idiomatic Haskell-way to act on predicates in 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 中的几个布尔条件然后根据它们的结果采取一些行动的惯用方法是什么?
忽略 Daniel 关于 races 的优点以及为什么经常不检查文件,更多 Haskell 解决方案通常是 monad 转换器。这是 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"
我觉得你发布的代码没问题。另一种可能性是像 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
我会使用 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
对于一些文件操作,我需要检查文件是否存在,是否被修改,然后才对其进行一些操作。我的新手 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 中的几个布尔条件然后根据它们的结果采取一些行动的惯用方法是什么?
忽略 Daniel 关于 races 的优点以及为什么经常不检查文件,更多 Haskell 解决方案通常是 monad 转换器。这是 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"
我觉得你发布的代码没问题。另一种可能性是像 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
我会使用 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