Почему ADT хороши, а наследование плохое? - PullRequest
21 голосов
/ 17 июля 2010

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

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

Спасибо.

Ответы [ 4 ]

27 голосов
/ 17 июля 2010

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

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

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

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

10 голосов
/ 17 июля 2010

Томас Петричек правильно понял основы;Возможно, вы захотите взглянуть на статью Фила Уодлера о «проблеме выражения».

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

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

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


PS Что ты читаешь?Я не знаю ни одного ответственного человека, который сказал бы: «АТП хорошо; ОО плохо».

9 голосов
/ 17 июля 2010

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

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

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

7 голосов
/ 18 июля 2010

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

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

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

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...