IO Monad 中的链式 if/else 语句
Chained if/else statements in the IO Monad
我想知道是否有一种惯用的方法可以在 IO Monad 中以命令式语言编写类似于链式 if/else 语句的控制代码。
所以在像 Python 这样的语言中,我通常会这样正确:
if οs.path.isdir(fname):
# do whatever
elif os.path.isfile(fname):
# ...
else:
# ...
我在 Haskell 中能想到的最好的是:
isf <- doesFileExist path
isd <- if isf then return False else doesDirectoryExist path
case (isf, isd) of
(True, _) -> return ...
(_, True) -> return ...
_ -> return ...
写的不太好,不知道有没有更好的写法
此外,为了验证我的理解:在 IO Monad 的情况下,isd <- ...
中的 if isf
部分是必需的,如果您不想始终执行这两个操作。我的猜测是,在其他 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"
与@chi的第二个建议类似,我更喜欢ifM
,因为它更接近命令式
如果我们不想涉及 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
然后代码结构类似于命令式语言中的代码结构:
test :: IO String
test = ifM anAction (do
putStrLn "branch a"
return "a")
$ ifM otherAction (do
putStrLn "branch b"
return "b")
$ return "none"
其中 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
我想知道是否有一种惯用的方法可以在 IO Monad 中以命令式语言编写类似于链式 if/else 语句的控制代码。
所以在像 Python 这样的语言中,我通常会这样正确:
if οs.path.isdir(fname):
# do whatever
elif os.path.isfile(fname):
# ...
else:
# ...
我在 Haskell 中能想到的最好的是:
isf <- doesFileExist path
isd <- if isf then return False else doesDirectoryExist path
case (isf, isd) of
(True, _) -> return ...
(_, True) -> return ...
_ -> return ...
写的不太好,不知道有没有更好的写法
此外,为了验证我的理解:在 IO Monad 的情况下,isd <- ...
中的 if isf
部分是必需的,如果您不想始终执行这两个操作。我的猜测是,在其他 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"
与@chi的第二个建议类似,我更喜欢ifM
,因为它更接近命令式
如果我们不想涉及 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
然后代码结构类似于命令式语言中的代码结构:
test :: IO String
test = ifM anAction (do
putStrLn "branch a"
return "a")
$ ifM otherAction (do
putStrLn "branch b"
return "b")
$ return "none"
其中 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