【问题标题】:What is the idiomatic Haskell-way to act on predicates in IO?在 IO 中作用于谓词的惯用 Haskell 方式是什么?
【发布时间】:2020-09-17 06:10:41
【问题描述】:

对于一些文件操作,我需要检查文件是否存在,是否被修改,然后才对其进行一些操作。我的新手 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 中的多个布尔条件然后根据其结果采取一些行动的惯用方法是什么?

【问题讨论】:

  • 并不是 Haskell 特有的东西,但是:我不会打扰 doesFileExist。而是捕获 isModified 在不存在时抛出的异常。否则你有一个竞争条件。
  • 这是一个不错的函数式编程,当然也很容易理解,这在我看来是一种品质。如果你想“链接”它,你可以寻找一个在 LHS IO 返回 True 时评估 RHS 的中缀运算符。此外, Either 单子可能会有所帮助。

标签: haskell conditional-statements predicate io-monad


【解决方案1】:

忽略丹尼尔关于比赛的好观点以及为什么通常不检查文件的原因,更多的 Haskell 解决方案通常是一个 monad 转换器。这是一个例外变压器有意义的典型情况。我还包括了对 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"

【讨论】:

  • 这真的不再是滥用ContT 而不是其他任何用途吗? :)
【解决方案2】:

您发布的代码对我来说看起来不错。另一种可能性是像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

【讨论】:

    【解决方案3】:

    我会使用whenM :: Monad m =&gt; m Bool -&gt; m () -&gt; m()ifM :: Monad m =&gt; m Bool -&gt; m a -&gt; m a -&gt; 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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-01-12
      • 2017-07-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-01
      • 1970-01-01
      相关资源
      最近更新 更多