Подтвердить что ты не робот

Почему ADT хороши, а наследование плохое?

Я долго программист OO и новичок в функциональном программировании. Из моего небольшого воздействия алгебраические типы данных выглядят только как особый случай наследования для меня, где у вас есть только одна иерархия уровней, а суперкласс не может быть расширен за пределами модуля.

Итак, мой (потенциально немой) вопрос: если только ADT - это особый случай наследования (опять же это предположение может быть неправильным, пожалуйста, исправьте меня в этом случае), то почему наследование получает всю критику, и ADT получают все похвалы?

Спасибо.

4b9b3361

Ответ 1

Я думаю, что ADT дополняют наследование. Оба они позволяют создавать расширяемый код, но способ работы расширяемости отличается:

  • ADTs позволяют легко добавлять новые функции для работы с существующими типами
    • Вы можете легко добавить новую функцию, которая работает с ADT, которая имеет фиксированный набор случаев
    • С другой стороны, добавление нового случая требует изменения всех функций
  • Наследование позволяет легко добавлять новые типы при фиксированной функциональности
    • Вы можете легко создать унаследованный класс и реализовать фиксированный набор виртуальных функций
    • С другой стороны, добавление новой виртуальной функции требует модификации всех унаследованных классов

Как объектно-ориентированный мир, так и функциональный мир разработали способы, позволяющие другим типам расширяемости. В Haskell вы можете использовать typeclasses, в ML/OCaml, люди будут использовать словарь функций или, возможно, (?) Функторов, чтобы получить расширяемость в стиле наследования. С другой стороны, в ООП люди используют шаблон посетителя, который по существу является способом получить что-то вроде ADT.

Обычные шаблоны программирования различаются в ООП и FP, поэтому, когда вы программируете на функциональном языке, вы пишете код таким образом, который требует расширения функционального стиля чаще (и аналогично в ООП). На практике я считаю, что здорово иметь язык, который позволяет использовать оба стиля в зависимости от проблемы, которую вы пытаетесь решить.

Ответ 2

Томас Петричек правильно определил основы; вы также можете посмотреть на Фила Вадлера, написавшего "проблему с выражением".

Есть две причины, по которым некоторые из нас предпочитают алгебраические типы данных над наследованием:

  • Используя алгебраические типы данных, компилятор может (и делает) сказать вам, если вы забыли случай или если случай избыточен. Эта способность особенно полезна, когда есть много операций над вещами, чем есть вещи. (Например, гораздо больше функций, чем алгебраические типы данных, или многие другие методы, чем конструкторы OO.) В объектно-ориентированном языке, если вы оставите метод вне подкласса, компилятор не может определить, является ли это ошибкой или вы планируете чтобы наследовать метод суперкласса без изменений.

  • Это более субъективно: многие люди отметили, что если наследование используется правильно и агрессивно, реализация алгоритма может быть легко размазана более чем на полдюжины классов и даже с хорошим браузером классов в может быть трудно следовать логике программы (поток данных и поток управления). Без хорошего браузера классов у вас нет шансов. Если вы хотите увидеть хороший пример, попробуйте внедрить bignums в Smalltalk, с автоматическим переходом на бонусы при переполнении. Это отличная абстракция, но язык затрудняет выполнение. Используя функции на алгебраических типах данных, логика вашего алгоритма обычно находится в одном месте или если она разделена, ее разделение на функции, которые имеют легко понятные контракты.


P.S. Что ты читаешь? Я не знаю ни одного ответственного человека, который говорит: "ADTs хорошо, OO плохо".

Ответ 3

По моему опыту, то, что люди обычно считают "плохим" в отношении наследования, реализованное большинством языков OO, - это не идея самого наследования, а идея подклассов, изменяющих поведение методов, определенных в суперклассе (переопределение метода), специально в присутствии изменяемого состояния. Это действительно последняя часть, что кикер. Большинство языков OO рассматривают объекты как "инкапсулирующее состояние", что позволяет разрешить беспорядочную мутацию состояния внутри объектов. Поэтому возникают проблемы, когда, например, суперкласс ожидает определенного метода для изменения частной переменной, но подкласс переопределяет метод, чтобы сделать что-то совершенно другое. Это может ввести тонкие ошибки, которые компилятор не может предотвратить.

Обратите внимание, что в реализации Haskell полиморфизма подкласса изменчивое состояние не разрешено, поэтому у вас нет таких проблем.

Также см. это возражение к концепции подтипирования.

Ответ 4

Я долго программист OO и новичок в функциональном программировании. Из моего небольшого воздействия алгебраические типы данных выглядят только как особый случай наследования для меня, где у вас есть только одна иерархия уровней, а суперкласс не может быть расширен за пределами модуля.

Вы описываете типы закрытых сумм, наиболее распространенную форму алгебраических типов данных, как показано в F # и Haskell. В принципе, все согласны с тем, что они являются полезной функцией в системе типов, в первую очередь потому, что сопоставление шаблонов облегчает их рассеивание по форме, а также по содержанию, а также потому, что они позволяют выполнять исчерпывающую и избыточную проверку.

Однако существуют и другие формы алгебраических типов данных. Важным ограничением традиционной формы является то, что они закрыты, что означает, что ранее определенный тип закрытой суммы не может быть расширен с помощью конструкторов нового типа (часть более общей проблемы, известной как "проблема выражения" ). Полиморфные варианты OCaml допускают как открытый, так и закрытый тип сумм и, в частности, вывод типов сумм. Напротив, Haskell и F # не могут вывести типы сумм. Полиморфные варианты решают проблему выражения, и они чрезвычайно полезны. Фактически, некоторые языки построены целиком на расширяемых алгебраических типах данных, а не на замкнутых типах сумм.

В крайнем случае у вас также есть такие языки, как Mathematica, где "все - это выражение". Таким образом, единственный тип в системе типов образует тривиальную "одноэлементную" алгебру. Это "расширяемость" в том смысле, что она бесконечна и, опять же, она достигает высшей точки в совершенно другом стиле программирования.

Итак, мой (потенциально немой) вопрос: если только ADT - это особый случай наследования (опять же это предположение может быть неправильным, пожалуйста, исправьте меня в этом случае), то почему наследование получает всю критику, и ADT получают все похвалы?

Я полагаю, что вы конкретно ссылаетесь на наследование реализации (т.е. переопределяете функциональность из родительского класса), в отличие от наследования интерфейсов (т.е. реализуя согласованный интерфейс). Это важное различие. Наследование реализации часто ненавидимо, тогда как наследование интерфейса часто нравится (например, в F #, которое имеет ограниченную форму ADT).

Вам действительно нужны как ADT, так и наследование интерфейсов. Языки, такие как OCaml и F #, предлагают оба.