Я буду використовувати мовно-агностичний опис монад на кшталт цього, спочатку опишу моноїди:
Моноїд це (приблизно) набір функцій , які приймають певний тип в якості параметра і повертають один і той же тип.
Монада є (приблизно) набір функцій , які приймають обгортки типу в якості параметра і повертає один і той же тип обгортки.
Зауважте, що це описи, а не визначення. Не соромтеся атакувати цей опис!
Так, мовою ОО, монада дозволяє виконувати такі операції, як:
Flier<Duck> m = new Flier<Duck>(duck).takeOff().flyAround().land()
Зауважте, що монада визначає та керує семантикою цих операцій, а не класом, що міститься.
Традиційно в мові ОО ми використовували ієрархію класів і успадкування для надання цієї семантики. Таким чином , ми будемо мати Bird
клас з методами takeOff()
, flyAround()
і land()
, і качка успадкує ті.
Але тоді ми потрапляємо в біду з нелітаючими птахами, бо penguin.takeOff()
не вдається. Ми маємо вдатися до викидів та поводження з винятками.
Крім того, як тільки ми кажемо, що Пінгвін - це Bird
, ми стикаємося з проблемами багаторазового успадкування, наприклад, якщо ми також маємо ієрархію Swimmer
.
По суті ми намагаємось класти класи в категорії (з вибаченнями для хлопців із Теорії категорій) та визначати семантику за категоріями, а не в окремих класах. Але монади здаються набагато зрозумілішим механізмом для цього, ніж ієрархії.
Тож у цьому випадку у нас буде Flier<T>
монада, як у наведеному вище прикладі:
Flier<Duck> m = new Flier<Duck>(duck).takeOff().flyAround().land()
... і ми ніколи не створимо це Flier<Penguin>
. Ми навіть можемо використовувати статичне введення тексту, щоб не допустити цього, можливо, із інтерфейсом маркера. Або перевірка можливостей виконання, щоб отримати заставу. Але дійсно, програміст ніколи не повинен ставити Пінгвіна в Flier, в тому ж сенсі вони ніколи не повинні ділитися на нуль.
Крім того, він більш загальноприйнятний. Летчик не повинен бути птахом. Наприклад Flier<Pterodactyl>
, або Flier<Squirrel>
, не змінюючи семантики цих окремих типів.
Після того, як ми класифікуємо семантику за складовими функціями на контейнері - замість ієрархій типів - вона вирішує старі проблеми з класами, які "роблять, роблять, не" вписуються в певну ієрархію. Це також легко і чітко дозволяє проводити кілька семантик для класу, як Flier<Duck>
і Swimmer<Duck>
. Схоже, ми боролися з невідповідністю імпедансу, класифікуючи поведінку за ієрархіями класів. Монади справляються з цим елегантно.
Отже, моє запитання полягає в тому, що ми також віддаємо перевагу складу над спадщиною, чи має сенс віддавати перевагу монадам над спадщиною?
(До речі, я не був впевнений, чи має це бути тут, чи в Comp Sci, але це здається більше проблемою практичного моделювання. Але, можливо, там краще.)