【问题标题】:Chained if/else statements in the IO MonadIO Monad 中的链式 if/else 语句
【发布时间】:2015-11-08 10:34:51
【问题描述】:

我想知道在 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 &lt;- ... 中的 if isf 部分是必需的。我的猜测是,在其他 Monad(惰性 Monad?)中,这将是不需要的,因为 isd 会被惰性评估。

编辑

基于第一个 cmets,我最终得到以下结果:

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,因为它更接近命令式版本。

【问题讨论】:

  • 但即使在 python 中你也应该重构那些 ;)
  • 好的,我会咬的,怎么样? :)
  • 好吧,你可以做一些事情,比如建立一个动作列表,并使用foldMforM等东西来获得你想要的结果。这将推广到任意数量的elifs,尽管仅对 3 个案例会很麻烦。在 python 中也是如此:for test, action in tests_and_actions: if test(input): action(input).
  • @ynimous 例如分成 3 个函数/动作,每个 guarding ;) 然后你可以对它们进行排序 ;) (当然 when 也可以)
  • 你可以制作ifM版本slightly nicer

标签: haskell monads


【解决方案1】:

如果我们不想涉及单子转换器,一个基本的选择是滚动我们自己的单子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

【讨论】:

  • 我觉得我更喜欢ifM。你会如何使用一元转换器?
  • @ynimous 我尝试了 monad 转换器 (MaybeT),但我不喜欢结果:lifts 和样板代码太多。也许其他人可以发布一些实际上可读的代码。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-04-09
  • 2015-03-13
  • 2015-10-28
  • 2017-02-05
  • 1970-01-01
  • 1970-01-01
  • 2015-07-06
相关资源
最近更新 更多