在IO Monad中链接if/else语句

yni*_*ous 6 monads haskell

我想知道是否有一种惯用的方法来编写类似于IO Monad中命令式语言中的链式if/else语句的控制代码.

所以在像Python这样的语言中,我通常会这样:

if ?s.path.isdir(fname):
    # do whatever
elif os.path.isfile(fname):
    # ...
else:
    # ...
Run Code Online (Sandbox Code Playgroud)

我可以在Haskell中找到的最好成绩如下:

isf <- doesFileExist path
isd <- if isf then return False else doesDirectoryExist path
case (isf, isd) of
    (True,  _)      -> return ...
    (_,     True)   -> return ...
    _               -> return ...
Run Code Online (Sandbox Code Playgroud)

这不是那么好,我想知道是否有更好的方法来写这种东西.

另外,为了验证我的理解:如果您不想总是同时执行这两个操作,则在IO Monad的情况下需要输入if isf部分isd <- ....我的猜测是在其他Monads(懒惰的Monads?)中,这不需要,因为isd会被懒惰地评估.

编辑

根据第一条评论,我最终得到以下结论:

firstMatchM :: (Monad m) => a -> [(a -> m Bool, b)] -> b -> m b
firstMatchM arg [] def   = return def
firstMatchM arg ((check,x):xs) def = do
    t <- check arg
    if t then return x else firstMatchM arg xs def

doFirstM :: (Monad m) => a -> [(a -> m Bool, a -> m b)] -> (a -> m b) -> m b
doFirstM arg acts def = do
    fm <- firstMatchM arg acts def
    fm arg

handlePath2 path = doFirstM path
   [( \p -> doesFileExist p,
         \p ->  return "file"
   ),(\p -> doesDirectoryExist p,
         \p -> return "dir"
   )] $ \p -> return "Error"
Run Code Online (Sandbox Code Playgroud)

这与@ chi的第二个建议类似,我更喜欢ifM,因为它更接近命令式版本.

chi*_*chi 4

如果我们不想涉及 monad 转换器,一个基本的选择是滚动我们自己的 monadic if

ifM :: Monad m => m Bool -> m a -> m a -> m a
ifM act t e = do
  b <- act
  if b then t else e
Run Code Online (Sandbox Code Playgroud)

那么代码结构就和命令式语言类似:

test :: IO String
test = ifM anAction (do
          putStrLn "branch a"
          return "a")
       $ ifM otherAction (do
          putStrLn "branch b"
          return "b")
       $ return "none"
Run Code Online (Sandbox Code Playgroud)

在哪里anAction, otherAction :: IO Bool

或者,使用类似的东西

ifChain :: [(IO Bool, IO a)] -> IO a -> IO a
ifChain [] e = e
ifChain ((g, a) : acts) e = do
   b <- g
   if b then a else ifChain acts e
Run Code Online (Sandbox Code Playgroud)