Следует ли мне избежать использования Monad?
Я новичок в Haskell и медленно понял, что что-то не так с существованием Monad терпит неудачу. Real World Haskell предупреждает об использовании ( "Еще раз рекомендуем вам всегда избегать использования сбоя!" ). Я только что заметил сегодня, что Росс Патерсон назвал его "бородавкой, а не образцом дизайна" еще в 2008 году (и, похоже, получил довольно определенное согласие в том, что нить).
Во время просмотра Dr Ralf Lämmel поговорите о сути функционального программирования, я начал понимать возможное напряжение, которое могло привести к тому, что Монада потерпит неудачу. В лекции Ральф говорит о добавлении различных монадических эффектов к базовому монадическому парсеру (каротаж, состояние и т.д.). Многие из эффектов потребовали изменения в базовом анализаторе, а иногда и в используемых типах данных. Я полагал, что добавление "неудачи" ко всем монадам могло быть компромиссом, потому что "провал" настолько распространен, и вы хотите как можно больше избегать изменений в "базовом" парсере (или любом другом). Конечно, какой-то "провал" имеет смысл для парсеров, но не всегда, скажем, put/get of state или ask/local Reader.
Сообщите мне, если я могу быть на неправильном пути.
Должен ли я избегать использования Monad?
Каковы альтернативы Монаду?
Существуют ли альтернативные библиотеки монад, которые не включают эту "бородавку"?
Где я могу больше узнать об истории вокруг этого проектного решения?
Ответы
Ответ 1
Некоторые монады имеют разумный механизм отказа, например. терминальная монада:
data Fail x = Fail
Некоторые монады не имеют разумного механизма отказа (undefined
неразумно), например. исходная монада:
data Return x = Return x
В этом смысле ясно, что бородавка требует, чтобы все монады имели метод fail
. Если вы пишете программы, которые абстрагируются над монадами (Monad m) =>
, не очень полезно использовать этот общий метод m
fail
. Это приведет к функции, которую вы можете создать с помощью монады, где fail
не должно существовать действительно.
Я вижу меньше возражений против использования fail
(особенно косвенно, путем сопоставления Pat <- computation
) при работе в конкретной монаде, для которой четко указано хорошее поведение fail
. Такие программы, мы надеемся, выживут в результате возвращения к старой дисциплине, где нетривиальное сопоставление шаблонов создало спрос на MonadZero
вместо просто Monad
.
Можно утверждать, что лучшая дисциплина всегда заключается в явном рассмотрении случаев отказа. Я возражаю против этой позиции по двум пунктам: (1), что точка монадического программирования состоит в том, чтобы избежать такого беспорядка, и (2), что текущая нотация для анализа случая на результате монадического вычисления настолько ужасна. Следующий выпуск SHE будет поддерживать обозначение (также найдено в других вариантах)
case <- computation of
Pat_1 -> computation_1
...
Pat_n -> computation_n
который может немного помочь.
Но вся эта ситуация - жалкая беспорядок. Часто полезно охарактеризовать монады с помощью операций, которые они поддерживают. Вы можете видеть fail
, throw
и т.д. Как операции, поддерживаемые некоторыми монадами, но не другие. Haskell делает его довольно неуклюжим и дорогостоящим для поддержки небольших локализованных изменений в множестве доступных операций, вводя новые операции, объясняя, как обращаться с ними по старым. Если мы серьезно хотим сделать более аккуратную работу здесь, нам нужно переосмыслить, как работает catch
, чтобы сделать его переводчиком между различными локальными механизмами обработки ошибок. Я часто хочу скопировать вычисление, которое может завершиться неудачно (например, с ошибкой совпадения шаблонов) с обработчиком, который добавляет больше контекстуальной информации, прежде чем передавать ошибку. Я не могу не чувствовать, что иногда это бывает сложнее, чем должно быть.
Итак, это может быть лучше, но, по крайней мере, используйте fail
только для определенных монадов, которые предлагают разумную реализацию и правильно обрабатывают "исключения".
Ответ 2
В Haskell 1.4 (1997) не было fail
. Вместо этого существовал класс типа MonadZero
, который содержал метод zero
. Теперь в обозначении do
используется zero
, чтобы указать неудачу совпадения шаблонов; это вызвало удивление людей: нужна ли их функция Monad
или MonadZero
, как они использовали нотацию do
в ней.
Когда Haskell 98 был разработан немного позже, они сделали несколько изменений, чтобы упростить программирование для новичков. Например, понимание монады превратилось в понимание списка. Аналогично, чтобы удалить проблему класса типа do
, класс MonadZero
был удален; для использования do
метод fail
был добавлен к Monad
; и для других применений zero
, mzero
был добавлен метод MonadPlus
.
Есть, я думаю, хороший аргумент в пользу того, что fail
нельзя использовать для чего-либо явно; его единственное предназначение - в переводе обозначений do
. Тем не менее, я сам часто непослушный и явно использую fail
.
Вы можете получить доступ к исходным отчетам 1.4 и 98 здесь. Я уверен, что обсуждение, ведущее к изменению, можно найти в некоторых архивах списка адресов электронной почты, но у меня нет ссылок.
Ответ 3
Я стараюсь избегать Монады, когда это возможно, и есть множество способов захвата неудачи в зависимости от ваших обстоятельств. Эдвард Ян написал хороший обзор в своем блоге в статье под названием 8 способов сообщить об ошибках в Haskell.
Таким образом, различные способы сообщать об ошибках, которые он идентифицирует, следующие:
- Использовать ошибку
- Использовать Может быть
- Использовать любую строку a
- Использовать Monad и не обобщать 1-3
- Использование MonadError и настраиваемый тип ошибки
- Использовать бросок в монаде IO
- Использовать ioError и catch
- Пойдите гайками с монадными трансформаторами
- Проверенные исключения
- Отказ
Из них у меня возникнет соблазн использовать параметр 3 с Either e b
, если я знаю, как обрабатывать эту ошибку, но нужно немного больше контекста.