В этом ответе я хочу обсудить тему, почему 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
В чем здесь проблема?Представьте, что у вас есть имя с пробелом между ними, например Джон М. Доу .В этом случае вся конструкция будет _|_
.Конечно, вы можете обойти это, добавив специальную функцию с let
, но это чистый пример.В то время, когда был создан Haskell 98, не было такой системы исключений, как сегодня, где вы могли бы просто отловить неудачное сопоставление с образцом.Кроме того, неполные шаблоны считаются плохим стилем кодирования.
Какое решение?Создатели Haskell 98 добавили специальную функцию fail
, вызываемую в случае несоответствия.Desugaring выглядит примерно так:
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 все еще остается большой вопрос: не плохая ли идея добавить что-то совершенно независимое к классу типов Monad
, например fail
?Я не могу ответить на этот вопрос, но ИМХО это решение было правильным, так как в других местах это не так удобно для fail
.