Відповіді:
Використання Maybe
(або його двоюрідного брата), Either
яке працює в основному так само, але дозволяє повернути довільне значення замість Nothing
), служить трохи іншим цілям, ніж винятки. З точки зору Java, це схоже на перевірений виняток, а не на виняток під час виконання. Він представляє щось очікуване, з чим вам доведеться зіткнутися, а не помилку, якої ви не очікували.
Таким чином, така функція, як 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
як монада - це уникати цього. Ви можете кодувати семантику розповсюдження у функції прив'язки, щоб ви отримали перевірку нуля / помилки безкоштовно. Єдиний раз, коли вам доведеться чітко перевірити, - якщо ви хочете повернути щось інше, ніж Nothing
дане a Nothing
, і навіть тоді це легко: існує маса стандартних функцій бібліотеки, щоб зробити цей код приємнішим.
Нарешті, ще одна перевага полягає в тому, що a Maybe
/ Either
type просто простіший. Не потрібно розширювати мову додатковими ключовими словами чи контрольними структурами - все це лише бібліотека. Оскільки вони є лише нормальними значеннями, це робить тип типу простішим - у Java вам потрібно розмежовувати типи (наприклад, тип повернення) та ефекти (наприклад, throws
заяви), де ви б не використовували Maybe
. Вони також поводяться так само, як і будь-який інший визначений користувачем тип - немає необхідності мати спеціальний код обробки помилок на мові.
Ще одна виграш полягає в тому, що Maybe
/ Either
є функтори та монади, а це означає, що вони можуть скористатися існуючими функціями управління монадою потоку (яких існує досить велика кількість) і, як правило, грати добре разом з іншими монадами.
Однак, є деякі застереження. Для одного - ні, Maybe
ні Either
замінюйте неперевірені винятки. Ви хочете отримати інший спосіб впоратися з такими речами, як поділ на 0, просто тому, що кожне ділення поверне Maybe
значення.
Інша проблема полягає в тому, що повернення декількох типів помилок (це стосується лише Either
). За винятком, ви можете кидати будь-які різні типи винятків в одній і тій же функції. з Either
, ви отримуєте лише один тип. Це можна подолати за допомогою підтипу або ADT, що містить усі різні типи помилок як конструктори (цей другий підхід - це те, що зазвичай використовується в Haskell).
І все-таки я більше віддаю перевагу Maybe
/ Either
підходу, оскільки вважаю його більш простим та гнучким.
OpenFile()
може кинути FileNotFound
або NoPermission
або і TooManyDescriptors
т.д. Ні не несе цю інформацію.if None return None
висловлювань -style.Найголовніше, що виняток і монада "Можливо" мають різні цілі - виняток використовується для позначення проблеми, а "Можливо".
"Медсестра, якщо у кімнаті 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 ()
.)
"Можливо" не є заміною винятків. Винятки мають бути використані у виняткових випадках (наприклад: відкриття db-з'єднання та db-сервера немає, хоча це має бути). "Можливо" - це для моделювання ситуації, коли у вас може бути або не бути дійсного значення; скажімо, ви отримуєте значення зі словника для ключа: воно може бути там, а може бути і немає - немає нічого "виняткового" в будь-якому з цих результатів.
Я другий відповідь Тихона, але я думаю, що є дуже важливий практичний момент, якого всі пропускають:
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’
});
В основному, обіцянка - це об'єкт, який:
В основному, оскільки підтримка рідного винятку мови не працює, коли обчислення відбувається в декількох потоках, реалізація обіцянок повинна забезпечити механізм керування помилками, і вони виявляються монадами, схожими на Haskell Maybe
/ Either
типи.
Система типу Haskell вимагає від користувача визнати можливість a Nothing
, тоді як мови програмування часто не вимагають, щоб вилучення було виключено. Це означає, що під час компіляції ми будемо знати, що користувач перевірив помилку.
throws NPE
до кожного підпису та catch(...) {throw ...}
до кожного окремого методу. Але я вважаю, що існує ринок перевірених в тому ж сенсі, що і у Можливо: зведена нульовість є необов'язковою і відслідковується в системі типів.
Монада, можливо, є такою ж, як і використання більшості мов основної мови для перевірки "null означає помилку" (за винятком випадків, коли вона вимагає перевірки нуля), і має в основному ті ж переваги та недоліки.
Maybe
числа, записавши a + b
без необхідності перевірки None
, і результат знову є необов'язковим значенням.
Maybe
типу , але використання Maybe
як монади додає синтаксичний цукор, що дозволяє виражати нульову логіку набагато більш елегантно.
Поводження з винятками може бути справжнім болем для факторингу та тестування. Я знаю, що python забезпечує гарний синтаксис "з", що дозволяє відловлювати винятки без жорсткого блоку "спробувати ... зловити". Але в Java, наприклад, спробуйте ловити блоки великі, шаблонні, або багатослівні, або надзвичайно багатослівні, і їх важко розбити. На додаток до цього, Java додає весь шум навколо перевірених та неперевірених винятків.
Якщо замість цього ваша монада ловить винятки і розглядає їх як властивість монадичного простору (замість аномалії обробки), то ви можете змішувати та співставляти функції, які ви прив'язуєте до цього простору незалежно від того, що вони кидають чи ловлять.
Якщо ще краще, ваша монада перешкоджає умовам, коли можуть статися винятки (наприклад, натискання нульової перевірки на "Можливо"), то ще краще. якщо ... то набагато, набагато простіше піддавати тестування та тестування, ніж спробувати ... зловити.
З того, що я бачив, Go застосовує аналогічний підхід, уточнюючи, що кожна функція повертається (відповідь, помилка). Це на зразок того, як "підняти" функцію в простір монади, де тип відповіді в ядрі прикрашений вказівкою про помилку, і фактично викидання та ловлення винятків у бік кроку.