这个问题在设计代码中已经多次出现过,特别是库。似乎有some interest,所以我认为它可能会成为一个很好的社区维基。
Monad中的fail
方法被某些人视为疣;这个课程有点武断,不属于原始范畴理论。但是当然在当前状态下,许多Monad类型都具有逻辑和有用的fail
实例。
MonadPlus类是Monad的一个子类,它提供了一个mzero
方法,它在逻辑上封装了monad中失败的概念。
因此,想要编写一些执行某种失败处理的monadic代码的库设计者可以选择让他的代码在Monad中使用fail
方法或者将他的代码限制为MonadPlus类,这样他就可以了使用mzero
会感觉很好,即使他根本不关心幺正组合mplus
操作。
关于此主题的一些讨论在此Wiki页面中有关proposals to reform the MonadPlus class.
的内容所以我想我有一个具体问题:
哪些monad实例(如果有)具有自然的fail
方法,但不能是MonadPlus的实例,因为它们没有mplus
的逻辑实现?
但我最感兴趣的是关于这个问题的讨论。谢谢!
编辑:最后一个想法发生在我身上。我最近学到了(即使它在fail
的文档中就是正确的),monadic“do”符号以模式匹配失败的方式被去除,如(x:xs) <- return []
中调用monad的fail
}。
似乎语言设计者必须受到一些自动故障处理的前景的强烈影响,因为haskell的语法在Monad中包含fail
。
答案 0 :(得分:10)
想想Either
。它的monadic实例看起来像这样:
{-# LANGUAGE FlexibleInstances #-}
instance Monad (Either String) where
(Left x) >>= _ = Left x
(Right a) >>= f = f a
return = Right
fail = Left
(我们需要 FlexibleInstances 才能允许像Either String
这样的实例)
所以它基本上像Maybe
,如果发生了某些事情,可能会出现错误信息。您无法使用mzero
重新创建此项,因为您无法向失败添加错误消息。它与fail
略有不同。
mplus
的每个实例都应满足这两个定律:
mzero `mplus` a -> a
a `mplus` mzero -> a
简单,不是吗?但是这些法律使mplus
变得特别。有了它们,就可以为它编写一个合理的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
将在第一次计算时停止,而[]
则尝试第二次计算。 [] >> [1..4] -> []
[] `mplus` [1..4] -> [1,2,3,4]
也表现得像这样:
MonadPlus
这只是为了讨论mplus
的各个方面,特别是与(>>)
形成对比的{{1}}方面。
答案 1 :(得分:1)
在这个答案中,我想讨论这个主题,为什么fail
是Monad
的成员。我不想将此添加到我的other answer,因为它涵盖了另一个主题。
虽然monad的数学定义不包含fail
,但Haskell 98的创建者将其放入Monad
类型类中。为什么呢?
为了简化monad的使用并使其更容易抽象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
转换为:
putStr "What's your full name? " >>
getLine >>= return . words >>= \[name,surname] ->
putSr "How old are you? " >>
getLine >>= return . read >>= \age ->
if age >= 18
then putStrLn $ "Hello Mr / Ms " ++ surname
else putStrLn $ "Hello " ++ name
这里有什么问题?想象一下,你的名字中间有一个空格,比如 Jon M. Doe 。在这种情况下,整个构造将是_|_
。当然,你可以通过添加let
的一些特殊功能来解决这个问题,但这是纯粹的样板。在创建Haskell 98时,没有像今天这样的异常系统,您可以简单地捕获失败的模式匹配。此外,不完整的模式被认为是糟糕的编码风格。
解决方案是什么? Haskell 98的创建者添加了一个特殊函数fail
,在不匹配的情况下调用。脱毛看起来有点像这样:
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
(我不确定是否真的有helper2
,但我认为是这样)
如果你看两次,你会发现它有多聪明。首先,从来没有一个不完整的模式匹配,其次你可以使fail
可配置。为实现这一目标,他们只是将fail
放入monad定义中。例如,对于Maybe
,fail
只是Nothing
,对于Either String
的实例,它是Left
。这样,编写monad独立的monadic代码就很容易了。例如,很长一段时间lookup
被定义为(Eq a,Monad b) => a -> [(a, b)] -> m b
,lookup
如果没有匹配则返回fail
。
现在,Haskell社区仍然存在一个重要问题:添加完全独立于Monad
类型类fail
类型的东西并不是一个坏主意吗?我不能回答这个问题,但恕我直言这个决定是正确的,因为其他地方对fail
不太舒服。