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