在这个答案中,我想讨论这个话题,为什么fail
是Monad
. 我不想将此添加到我的其他答案中,因为它涵盖了另一个主题。
尽管 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
了 monads 定义。例如,对于Maybe
,fail
是简单Nothing
的,对于 的实例Either String
,它是Left
。这样,很容易编写独立于 monad 的 monadic 代码。例如,很长一段时间lookup
被定义为(Eq a,Monad b) => a -> [(a, b)] -> m b
,如果没有匹配则lookup
返回。fail
Monad
现在,在 Haskell 社区中仍然存在一个大问题:向类型类添加完全独立的东西不是一个坏主意fail
吗?我无法回答这个问题,但恕我直言,这个决定是正确的,因为其他地方对fail
.