在这个答案中,我想讨论一个话题,即为什么
fail
是
Monad
的成员。我不想将其添加到我的
其他回答中,因为它涵盖了另一个话题。
虽然单子的数学定义不包含
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
翻译为:
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
放入单子定义中。例如,对于Maybe
,fail
只是Nothing
,而对于Either String
的实例,则为Left
。这样,编写单子无关的单子代码就很容易。例如,很长一段时间以来,lookup
被定义为(Eq a,Monad b) => a -> [(a, b)] -> m b
,如果没有匹配,则lookup
返回fail
。
现在,在Haskell社区中仍然存在一个重要问题:像fail
这样完全独立于Monad
类型类的东西是否是一个坏主意?我无法回答这个问题,但我认为这个决定是正确的,因为其他地方并不那么适合fail
。
fail
的讨论,或者在MonadPlus维基讨论页面上扩展这种讨论的脉络。也许我的回答可以被编辑以更好地达到这个目标。 - jberryman