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

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

Таким образом, мой (потенциально немой) вопрос: Если ADTS просто, что, особый случай наследования (снова это предположение может быть неправильным; исправьте меня в этом случае), затем почему делает наследование, получает всю критику, и ADTS получают всю похвалу?

Спасибо.

21
задан one-zero-zero-one 17 July 2010 в 19:52
поделиться

4 ответа

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

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

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

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

28
ответ дан 29 November 2019 в 06:51
поделиться

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

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

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

9
ответ дан 29 November 2019 в 06:51
поделиться

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

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

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

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

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

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

На самом деле вам нужны и ADTs, и наследование интерфейсов. Такие языки, как OCaml и F#, предлагают и то, и другое.

7
ответ дан 29 November 2019 в 06:51
поделиться

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

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

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

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


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

.
10
ответ дан 29 November 2019 в 06:51
поделиться
Другие вопросы по тегам:

Похожие вопросы: