Ответы:
Использование Maybe
(или его двоюродного брата, Either
который работает в основном таким же образом, но позволяет вам возвратить произвольное значение вместо Nothing
), служит несколько иной цели, чем исключения. В терминах Java это похоже на наличие проверенного исключения, а не исключения во время выполнения. Он представляет собой нечто ожидаемое, с которым вам приходится иметь дело, а не ошибку, которую вы не ожидали.
Таким образом, функция like indexOf
будет возвращать Maybe
значение, потому что вы ожидаете, что элемент отсутствует в списке. Это очень похоже на возврат null
из функции, за исключением безопасного типа, который заставляет вас иметь дело с null
делом. Either
работает так же, за исключением того, что вы можете вернуть информацию, связанную с регистром ошибки, так что на самом деле это больше похоже на исключение, чем Maybe
.
Так в чем же преимущества подхода Maybe
/ Either
? С одной стороны, это первоклассный гражданин языка. Давайте сравним функцию, использующую функцию, Either
генерирующую исключение. В исключительном случае единственным реальным выходом из ситуации является try...catch
утверждение. Для этой Either
функции вы можете использовать существующие комбинаторы, чтобы сделать управление потоком более понятным. Вот пара примеров:
Во-первых, предположим, что вы хотите попробовать несколько функций, которые могут выдавать ошибку подряд, пока не получите функцию, которая этого не делает. Если вы не получите ничего без ошибок, вы хотите вернуть специальное сообщение об ошибке. Это на самом деле очень полезный шаблон, но это будет ужасная боль при использовании try...catch
. К счастью, поскольку Either
это просто нормальное значение, вы можете использовать существующие функции, чтобы сделать код намного понятнее:
firstThing <|> secondThing <|> throwError (SomeError "error message")
Другой пример - наличие дополнительной функции. Допустим, у вас есть несколько функций, в том числе одна, которая пытается оптимизировать запрос. Если это не удается, вы хотите, чтобы все остальное работало так или иначе. Вы можете написать код примерно так:
do a <- getA
b <- getB
optional (optimize query)
execute query a b
Оба эти случая яснее и короче, чем использование try..catch
, и, что более важно, более семантические. Использование функции типа <|>
или optional
делает ваши намерения намного понятнее, чем использование try...catch
для обработки исключений.
Также обратите внимание, что вам не нужно засорять ваш код такими строками if a == Nothing then Nothing else ...
! Весь смысл лечения Maybe
и Either
как монады состоит в том, чтобы избежать этого. Вы можете закодировать семантику распространения в функцию bind, так что вы бесплатно получаете проверки null / error. Единственный раз, когда вам нужно явно проверить, это если вы хотите вернуть что-то отличное от Nothing
данного Nothing
, и даже тогда это легко: есть множество стандартных библиотечных функций, которые делают этот код более приятным.
Наконец, еще одним преимуществом является то, что Maybe
/ Either
тип просто проще. Нет необходимости расширять язык дополнительными ключевыми словами или структурами управления - все это просто библиотека. Поскольку они являются просто обычными значениями, это упрощает систему типов - в Java вы должны различать типы (например, тип возвращаемого значения) и эффекты (например, throws
операторы), которые вы бы не использовали Maybe
. Они также ведут себя так же, как и любой другой определенный пользователем тип - нет необходимости иметь специальный код обработки ошибок, запеченный в языке.
Еще одна победа заключается в том, что Maybe
/ Either
являются функторами и монадами, что означает, что они могут использовать существующие функции управления потоком монад (которых достаточно) и, в общем, хорошо играть вместе с другими монадами.
Тем не менее, есть некоторые оговорки. Во-первых, Maybe
ни Either
заменить непроверенные исключения. Вам понадобится другой способ обработки таких вещей, как деление на 0, просто потому, что было бы больно, чтобы каждое деление возвращало Maybe
значение.
Другая проблема заключается в возвращении нескольких типов ошибок (это относится только к Either
). За исключением исключений, вы можете создавать любые разные типы исключений в одной и той же функции. с Either
, вы получите только один тип. Это можно преодолеть с помощью подтипа или ADT, содержащего все различные типы ошибок в качестве конструкторов (этот второй подход обычно используется в Haskell).
Тем не менее, в целом, я предпочитаю подход Maybe
/, Either
потому что считаю его более простым и более гибким.
OpenFile()
может бросить FileNotFound
или NoPermission
и TooManyDescriptors
т. д. Ни один не несет эту информацию.if None return None
инструкций -style.Самое главное, что исключение и монада Maybe имеют разные цели - исключение используется для обозначения проблемы, в то время как Maybe - нет.
«Медсестра, если в комнате 5 есть пациент, можете ли вы попросить его подождать?»
(обратите внимание на «если» - это означает, что врач ожидает монаду «Может быть»)
None
значения могут быть просто переданы). Ваш пункт 5 является лишь верным ... вопрос в том, какие ситуации однозначно исключительны? Как оказалось… не много .
bind
так, чтобы тестирование None
не вызывало синтаксических издержек. Очень простой пример, C # просто соответствующим образом перегружает Nullable
операторы. Не None
требуется проверка даже при использовании типа. Конечно, проверка все еще выполняется (она безопасна для типов), но негласно и не загромождает ваш код. То же самое относится в некотором смысле к вашему возражению против моего возражения против (5), но я согласен, что это не всегда применимо.
Maybe
с монадой состоит в том, чтобы сделать распространение None
неявным. Это означает, что если вы хотите вернуть None
данные None
, вам вообще не нужно писать какой-либо специальный код. Единственный раз, когда вам нужно соответствовать, если вы хотите сделать что-то особенное None
. Вам никогда не нужны if None then None
такие заявления.
null
точно так же проверяете (например if Nothing then Nothing
) бесплатно, потому что Maybe
это монада. Это закодировано в определении bind ( >>=
) для Maybe
.
Either
), которая ведет себя точно так же Maybe
. Переключение между ними на самом деле довольно просто, потому что Maybe
на самом деле это просто особый случай Either
. (В Haskell, вы можете думать , Maybe
как Either ()
.)
«Возможно» не является заменой исключений. Исключения предназначены для использования в исключительных случаях (например: открытие соединения с БД, а сервер БД отсутствует, хотя и должно быть). «Возможно» - для моделирования ситуации, когда вы можете иметь или не иметь действительное значение; скажем, вы получаете значение из словаря для ключа: оно может быть там или может не быть - нет ничего «исключительного» в любом из этих результатов.
Я второй ответ Тихона, но я думаю, что есть очень важный практический момент, который все упускают:
Either
Механизм не соединен с резьбой на всех.Итак, то, что мы наблюдаем в настоящее время в реальной жизни, это то, что многие решения для асинхронного программирования используют вариант Either
стиля обработки ошибок. Рассмотрим обещания Javascript , как подробно описано в любой из этих ссылок:
Концепция обещаний позволяет вам писать асинхронный код, подобный этому (взят из последней ссылки):
var greetingPromise = sayHello();
greetingPromise
.then(addExclamation)
.then(function (greeting) {
console.log(greeting); // 'hello world!!!!’
}, function(error) {
console.error('uh oh: ', error); // 'uh oh: something bad happened’
});
По сути, обещание - это объект, который:
По сути, поскольку встроенная поддержка исключений языка не работает, когда ваши вычисления выполняются в нескольких потоках, реализация обещаний должна предоставить механизм обработки ошибок, и они оказываются монадами, подобными типам Maybe
/ Either
типам Haskell .
Система типов Haskell потребует от пользователя подтверждения возможности a Nothing
, в то время как языки программирования часто не требуют, чтобы исключение было перехвачено. Это означает, что во время компиляции мы узнаем, что пользователь проверил наличие ошибки.
throws NPE
к каждой сигнатуре и catch(...) {throw ...}
к каждому телу метода. Но я верю, что существует рынок для check в том же смысле, что и с Maybe: nullability является необязательной и отслеживается в системе типов.
Возможно, монада в основном такая же, как и в большинстве основных языков проверки «null означает ошибку» (за исключением того, что требует проверки null), и имеет в основном те же преимущества и недостатки.
Maybe
числа, написав a + b
без необходимости проверки None
, и результат снова является необязательным значением.
Maybe
типа , но использование Maybe
в качестве монады добавляет синтаксический сахар, который позволяет выразить нуль-логику намного элегантнее.
Обработка исключений может быть реальной болью для факторинга и тестирования. Я знаю, что python предоставляет хороший синтаксис «with», который позволяет перехватывать исключения без жесткого блока «try ... catch». Но, например, в Java попробуйте блоки catch, которые являются большими, шаблонными, либо многословными, либо чрезвычайно многословными, и их трудно разбить. Вдобавок к этому Java добавляет весь шум вокруг проверенных и непроверенных исключений.
Если вместо этого ваша монада перехватывает исключения и обрабатывает их как свойство монадического пространства (вместо некоторой аномалии обработки), тогда вы можете смешивать и сопоставлять функции, которые вы привязываете к этому пространству, независимо от того, что они генерируют или ловят.
Если, что еще лучше, ваша монада предотвращает условия, при которых могут возникать исключения (например, установка нулевой проверки в Maybe), тогда еще лучше. если ... тогда гораздо, гораздо проще проанализировать и проверить, чем попробовать ... поймать.
Из того, что я видел, Go использует аналогичный подход, указывая, что каждая функция возвращает (ответ, ошибка). Это похоже на «поднятие» функции в пространство монад, где основной тип ответа украшен индикацией ошибки, а также исключениями, возникающими при отбрасывании и отлове.