Надлежащее использование Monad `fail` против MonadPlus` mzero` - PullRequest
20 голосов
/ 17 февраля 2011

Это вопрос, который несколько раз возникал у меня в коде проекта, особенно в библиотеках. Кажется, в этом есть некоторый интерес , поэтому я подумал, что это может стать хорошей вики для сообщества.

Метод fail в Монаде считается некоторыми бородавкой; несколько произвольное дополнение к классу, которое не происходит из исходной теории категорий. Но, конечно, в текущем состоянии многие типы монад имеют логические и полезные fail экземпляры.

Класс MonadPlus является подклассом Monad, который предоставляет метод mzero, который логически инкапсулирует идею сбоя в монаде.

Таким образом, разработчик библиотеки, который хочет написать некоторый монадический код, который выполняет какую-то обработку ошибок, может сделать так, чтобы его код использовал метод fail в Monad или ограничил его код классом MonadPlus, чтобы он мог чувствовать хороша в использовании mzero, хотя ему совершенно безразлична операция моноидального комбинирования mplus.

На этой вики-странице обсуждаются некоторые вопросы о предложениях по реформированию класса MonadPlus.


Так что, думаю, у меня есть один конкретный вопрос:

Какие экземпляры монад, если таковые имеются, имеют естественный метод fail, но не могут быть экземплярами MonadPlus, поскольку они не имеют логической реализации для mplus?

Но меня больше всего интересует обсуждение этой темы. Спасибо!


РЕДАКТИРОВАТЬ : Одна заключительная мысль пришла мне в голову. Недавно я узнал (хотя в документах для fail это прямо там), что монадическая нотация "do" обесцвечивается таким образом, что сбои совпадения с образцом, как в (x:xs) <- return [], вызывают монаду fail.

Похоже, что создатели языка, должно быть, находились под сильным влиянием перспективы некоторой автоматической обработки ошибок, встроенной в синтаксис haskll при включении fail в Monad.

Ответы [ 2 ]

10 голосов
/ 17 февраля 2011

Думайте о Either.Это монадический экземпляр выглядит следующим образом:

{-# 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 голос
/ 17 февраля 2011

В этом ответе я хочу обсудить тему, почему 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.

...