这是一个在设计代码中多次出现的问题,尤其是在库中。它里面似乎有some interest,所以我想它可能会成为一个很好的社区维基。
Monad中的fail
方法被一些人认为是一个瘤;这是对类的一个有点武断的添加,而不是来自原始的范畴理论。当然,在当前的情况下,许多Monad类型都有逻辑和有用的fail
实例。
MonadPlus类是monad的一个子类,它提供了一个mzero
方法,该方法在逻辑上将失败的概念封装在Monad中。
因此,想要编写一些执行某种故障处理的一元代码的库设计人员可以选择让他的代码在Monad中使用fail
方法,或者将他的代码限制在MonadPlus类中,这样他就可以很好地使用mzero
,即使他根本不关心一元组合mplus
操作。
在这个关于proposals to reform the MonadPlus class.的维基页面上有一些关于这个主题的讨论
所以我想我有一个特别的问题:
哪些monad实例(如果有)具有自然的fail
方法,但不能是MonadPlus的实例,因为它们没有mplus
__的逻辑实现?
但我最感兴趣的是关于这个话题的讨论。谢谢!
编辑:我想到了最后一个想法。我最近了解到(尽管它就在fail
的文档中),一元的"do“表示法是以这样一种方式去糖化的,即模式匹配失败,就像在(x:xs) <- return []
中调用单数的fail
。
看起来,语言设计者在Monad中包含fail
时,一定受到了内置到haskell语法中的一些自动故障处理的强烈影响。
发布于 2011-02-17 21:29:27
想想Either
吧。它的一元实例如下所示:
{-# LANGUAGE FlexibleInstances #-}
instance Monad (Either String) where
(Left x) >>= _ = Left x
(Right a) >>= f = f a
return = Right
fail = Left
(为了允许像Either String
这样的实例,我们需要FlexibleInstances )
因此,它基本上就像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
相对于(>>)
的一些方面。
发布于 2011-02-17 23:43:12
在这个回答中,我想讨论这个话题,为什么fail
是Monad
的成员。我不想将它添加到我的other answer中,因为它涵盖了另一个主题。
尽管单子的数学定义不包含fail
,但Haskell98的创建者将其放入了Monad
类型类中。为什么?
为了简化monads的使用并使其更容易抽象monads的用法,他们引入了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的时候,还没有像今天这样的异常系统,您可以简单地捕获失败的模式匹配。此外,不完整的模式被认为是糟糕的编码风格。
解决方案是什么?Haskell98的创建者添加了一个特殊的函数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
放入monads定义中。例如,对于Maybe
,fail
就是Nothing
,而对于Either String
实例,它就是Left
。通过这种方式,可以很容易地编写独立于monad的一元代码。例如,在很长一段时间内,lookup
被定义为(Eq a,Monad b) => a -> [(a, b)] -> m b
,如果不匹配,lookup
将返回fail
。
现在,在Haskell社区中仍然有一个大问题:向fail
这样的Monad
类型类添加一些完全独立的东西是不是一个坏主意?我不能回答这个问题,但我认为这个决定是正确的,因为其他地方对fail
来说并不那么舒服。
https://stackoverflow.com/questions/5023969
复制相似问题