Haskell Monad`fail`vs.MonadPlus`mzero的适当使用`

Haskell Monad`fail`vs.MonadPlus`mzero的适当使用`,haskell,monads,typeclass,monadplus,Haskell,Monads,Typeclass,Monadplus,这是我在设计代码中多次遇到的问题,尤其是库。这里面似乎有一些东西,所以我想它可能会成为一个很好的社区维基 Monad中的fail方法被一些人认为是一个缺点;对类的任意添加,而不是源于原始的范畴理论。当然,在当前的情况下,许多Monad类型都有逻辑和有用的fail实例 MonadPlus类是Monad的一个子类,它提供了一个mzero方法,逻辑上封装了Monad中的失败思想 因此,希望编写某种故障处理的单元代码的库设计师可以选择使用Monad中的fail方法,或者将代码限制在MonadPlus类中

这是我在设计代码中多次遇到的问题,尤其是库。这里面似乎有一些东西,所以我想它可能会成为一个很好的社区维基

Monad中的
fail
方法被一些人认为是一个缺点;对类的任意添加,而不是源于原始的范畴理论。当然,在当前的情况下,许多Monad类型都有逻辑和有用的
fail
实例

MonadPlus类是Monad的一个子类,它提供了一个
mzero
方法,逻辑上封装了Monad中的失败思想

因此,希望编写某种故障处理的单元代码的库设计师可以选择使用Monad中的
fail
方法,或者将代码限制在MonadPlus类中,这样他就可以对使用
mzero
感到满意,即使他根本不关心monoid组合
mplus
操作

关于这个主题的一些讨论在这个维基页面中,关于


所以我想我有一个特别的问题:

哪些monad实例(如果有的话)有一个自然的
fail
方法,但不能是MonadPlus的实例,因为它们没有
mplus
的逻辑实现

但我最感兴趣的是关于这个话题的讨论。谢谢



编辑:我想到了最后一个想法。我最近了解到(即使在
fail
的文档中也有),一元“do”符号的设计方式是模式匹配失败,比如
(x:xs)想想
或者
。它的一元实例如下所示:

{-# LANGUAGE FlexibleInstances #-}
instance Monad (Either String) where
  (Left x) >>= _   = Left x
  (Right a)  >>= f = f a
  return           = Right
  fail             = Left
[] >> [1..4] -> []
[] `mplus` [1..4] -> [1,2,3,4]
putStr "What's your full name? " >> let
  helper1 [name,surname] =
    putSr "How old are you? " >> let
      helper2 age =
        if age >= 18
           then putStrLn $ "Hello Mr / Ms " ++ surname
           else putStrLn $ "Hello " ++ name
      helper2 _ = fail "..."
    in getLine >>= return . read >>= helper2
  helper1 _ = fail "..."
in getLine >>= return . words >>= helper1
(我们需要FlexibleInstances,以便允许像
字符串这样的实例)
因此,它基本上类似于
可能
,如果发生了什么事情,它会有一条可选的错误消息。您无法使用
mzero
重新创建此错误,因为您无法向故障添加错误消息。这与
fail
有些不同

mplus
的每个实例都应满足以下两个定律:

mzero `mplus` a -> a
a `mplus` mzero -> a
很简单,不是吗?但是这些法律使MPLU变得特别。有了它们,就可以为它编写一个合理的
MonadPlus
实例:

instance MonadPlus (Either a) where
  mzero = Left undefined
  mplus (Left _) b = b
  mplus a _        = a
这是什么?它代表了一种选择。如果第一次计算成功,将返回它。否则,
mplus
返回第二次计算。注意它与不符合法律的
(>>)
有何区别:

Left a   >>    Right b -> Left a
Left a `mplus` Right b -> Right b
(>>)
将在第一次计算时停止,而
mplus
将尝试第二次计算<代码>[]
的行为如下:

{-# LANGUAGE FlexibleInstances #-}
instance Monad (Either String) where
  (Left x) >>= _   = Left x
  (Right a)  >>= f = f a
  return           = Right
  fail             = Left
[] >> [1..4] -> []
[] `mplus` [1..4] -> [1,2,3,4]
putStr "What's your full name? " >> let
  helper1 [name,surname] =
    putSr "How old are you? " >> let
      helper2 age =
        if age >= 18
           then putStrLn $ "Hello Mr / Ms " ++ surname
           else putStrLn $ "Hello " ++ name
      helper2 _ = fail "..."
    in getLine >>= return . read >>= helper2
  helper1 _ = fail "..."
in getLine >>= return . words >>= helper1

这只是为了讨论
MonadPlus
的方面,特别是
mplus
的方面。与
(>>)

相比,在这个回答中,我想讨论一个主题,为什么
失败
Monad
的isa成员。我不想把这个添加到我的文档中,因为它涉及到另一个主题


虽然单子的数学定义不包含
fail
,但Haskell 98的创建者将其放入
monad
类型类中。为什么?

为了简化单子的使用并使单子的使用更容易抽象,他们引入了
do
符号,这是一种非常有用的糖。例如,此代码:

do putStr "What's your full name? "
   [name,surname] <- getLine >>= return . words
   putStr "How old are you? "
   age <- getLine >>= return . read
   if age >= 18
      then putStrLn $ "Hello Mr / Ms " ++ surname
      else putStrLn $ "Hello " ++ name
这里有什么问题?想象一下,你有一个名字,中间有一个空格,比如Jon M.Doe。在这种情况下,整个构造将是
124; 124;
。当然,您可以通过使用
let
添加一些特殊函数来解决这个问题,但这只是一个纯样板。在Haskell 98创建时,没有像今天这样的异常系统,您可以简单地捕获失败的模式匹配。此外,不完整的模式被认为是糟糕的编码风格

解决办法是什么?Haskell 98的创建者添加了一个特殊函数
fail
,在不匹配的情况下调用。脱胶看起来有点像这样:

{-# LANGUAGE FlexibleInstances #-}
instance Monad (Either String) where
  (Left x) >>= _   = Left x
  (Right a)  >>= f = f a
  return           = Right
  fail             = Left
[] >> [1..4] -> []
[] `mplus` [1..4] -> [1,2,3,4]
putStr "What's your full name? " >> let
  helper1 [name,surname] =
    putSr "How old are you? " >> let
      helper2 age =
        if age >= 18
           then putStrLn $ "Hello Mr / Ms " ++ surname
           else putStrLn $ "Hello " ++ name
      helper2 _ = fail "..."
    in getLine >>= return . read >>= helper2
  helper1 _ = fail "..."
in getLine >>= return . words >>= helper1
(我不确定是否真的有
助手2
,但我想是的)

如果你再看两次,你就会发现它有多聪明。首先,从不存在不完整的模式匹配,其次,您可以使
fail
可配置。为了实现这一点,他们只需将
fail
放入monad定义中。例如,对于
Maybe
fail
只是
Nothing
,对于
任一字符串的实例,它是
Left
。这样,编写独立于单子的单子代码就很容易了。例如,在很长一段时间内,
lookup
被定义为
(等式a,Monad b)=>a->[(a,b)]->mb
,如果不存在匹配项,
lookup
返回
fail


现在,Haskell社区仍然存在一个大问题:在
Monad
类型类中添加一些完全独立的东西,比如
fail
,这不是一个坏主意吗?我不能回答这个问题,但我认为这个决定是对的,因为其他地方对
fail

不太舒服。这是一个不合适的问题,但在回答中可能有助于澄清问题。最重要的一步是决定MonadPlus应该遵循什么代数定律,但也值得考虑程序员可能期望fail和mzero做什么。我想我只是邀请大家讨论一下Monad中包含的
fail
,也许是上面MonadPlus wiki讨论页面中讨论脉络的扩展。也许我的答案可以编辑以更好地服务于这个目标。哦,大多数人不喜欢失败,认为它不应该存在于Monad中:-)将标记[class]更改为[typeclass],因为类几乎完全用于OO编程。Haskeller通常使用[typeclass]标记。哦,糟了。尽量不要在
(左a)>>=f=…
中缩短cirtuit,这就需要发明一个参数。即使是在那个时候