Haskell IO 的 MonadPlus 定义

14

我刚写了一点代码,想要在IO Monad中使用guard函数。然而,在IO中没有MonadPlus的定义,这意味着我们不能在IO领域使用guard。我看过一个例子,使用MaybeT转换器在Maybe Monad中使用guard并提升所有IO操作,但如果不必要,我不想这样做。

我想要的一些示例可能包括:

handleFlags :: [Flag] -> IO ()
handleFlags flags = do
    when (Help `elem` flags) (putStrLn "Usage: program_name options...")
    guard (Help `elem` flags)
    ... do stuff ...
    return ()

我想知道是否有一种不错的方式可以在IO Monad中通过对MonadPlus或其他声明来获得一个守卫函数(或类似的东西)。或者也许我做错了什么;有没有更好的方法在上面的函数中编写帮助消息?谢谢。

(附言:我可以使用if-then-else语句,但这似乎有些违背了初衷。更不用说对于很多选项来说,这将导致大量嵌套。)


注意:在此问题发布后的几个 GHC 版本中,IO 被赋予了 AlternativeMonadPlus,如 C. A. McCann 的回答 所描述的那样,包括注意事项。另请参见:MonadPlus IO 不是一个单子 - duplode
3个回答

23

考虑 MonadPlus 的定义:

class Monad m => MonadPlus m where
    mzero :: m a 
    mplus :: m a -> m a -> m a
你如何为IO实现mzero?类型IO a的值表示返回类型为a的IO计算,所以mzero必须是返回任何可能类型的IO计算。显然,没有办法为某个任意类型创造一个值,并且与Maybe不同,没有“空”构造函数可用,因此mzero必须代表从未返回的IO计算。
如何编写永远不会返回的IO计算?要么进入无限循环,要么抛出运行时错误。前者的效用存疑,因此后者是你卡住的方式。
简而言之,要为IO编写MonadPlus实例,您需要执行以下操作:让mzero抛出运行时异常,并使mplus在捕获由mzero引发的任何异常时评估其第一个参数。如果没有引发异常,则返回结果。如果引发异常,则评估mplus的第二个参数,同时忽略异常。
话虽如此,运行时异常通常被认为是不理想的,因此在走下这条路之前,我会犹豫。如果您确实希望以这种方式做(并且不介意增加程序在运行时崩溃的机会),则您将发现所有需要实现上述内容的内容都在Control.Exception中。
实际上,如果我想在评估单调表达式的结果上进行大量保护,我可能会使用monad变换器方法,或者如果大多数条件取决于作为函数参数提供的纯值(例如您示例中的标志)使用模式保护,如@Anthony的答案中所述。

那是一个很好的答案,让我明白了为什么在没有错误或无限循环时,IO MonadPlus 没有意义。 - Robert Massaioli
2
@Robert Massaioli:这并不意味着那种方式有任何*问题。这是一个完全正确的MonadPlus实例,它等价于使用Either e作为错误单子的MonadPlus工作方式,并且我认为某个monad转换库中甚至存在这样的IO实例。唯一的问题是你必须更加小心地捕获错误,因为与MaybeT或类似东西不同,异常可以一直传递到main - C. A. McCann

8

我通常使用守卫(guards)来完成这种操作。

handleFlags :: [Flag] -> IO ()
handleFlags flags
  | Help `elem` flags = putStrLn "Usage: program_name options..."
  | otherwise = return ()

2
有时候你知道自己在正确的轨道上(使用 guard),但是突然有人指出了正确的想法(这个),让你恍然大悟。谢谢,我用了这个,正是我想要的。虽然我标记了 camccann 的答案,因为他展示了我的想法为什么不好,并提出了我的替代方案,然后指向了你的解决方法作为他本来会做的事情。 - Robert Massaioli
1
请注意,有像cmdargs这样处理这些事情的库 :) - Alp Mestanogullari
就我个人而言,cmdargs 的魔法太多了,我不喜欢。我更喜欢 parseargs,因为它都是明文操作。 - sclv
我更喜欢这种方法,特别是如果你需要检查很多选项,但如果你只有一个选项,那么Control.Monad.when可能会有所帮助。(我知道这是一个旧的线程,但以防万一有人像我一样找到它) - Ivan Perez

0

有专门用于此的函数:在 Control.Monad 中,函数 when 和它的对应函数 unless。 安东尼的答案可以重写为:

handleFlags :: [Flag] -> IO ()
handleFlags flags =
    when (Help `elem` flags) $ putStrLn "Usage: program_name options..."

规格:

when :: (Applicative m) => Bool -> m () -> m ()
unless bool = when (not bool)

链接到hackage.haskell.org上的文档

如果需要更多,这里有一个链接到另一个包,具体是面向monad并且带有更多实用工具:Control.Monad.IfElse


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接