Отказ от наследования в языках программирования

10

Я разрабатываю свой собственный язык программирования. Это язык общего назначения (например, статический тип Python для рабочего стола int x = 1;), не предназначенный для облака.

Как вы думаете, это нормально, чтобы не допустить наследования или Mixins? (учитывая, что пользователь по крайней мере будет иметь интерфейсы)

Например: Google Go, системный язык, который потряс сообщество программистов, не допустив наследования.

Кристофер
источник

Ответы:

4

На самом деле, мы все больше и больше находим, что использование наследования является неоптимальным, поскольку (среди прочего) оно ведет к тесной связи.

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

Так что да , отказ от предоставления наследования, в частности, является полностью обоснованным и очень модным решением для проектирования.

Примеси, с другой стороны, даже не (пока) функция языка мейнстрим, и языки , которые действительно обеспечивают функцию под названием «Mixin» часто понимают очень разные вещи им. Не стесняйтесь предоставить это или нет. Лично я нахожу это очень полезным, но реализовать его правильно может быть очень сложно.

Конрад Рудольф
источник
13

Рассуждать о том, необходимо ли наследование (или какая-либо отдельная особенность на самом деле) или нет, без учета остальной семантики языка, бессмысленно; ты споришь в вакууме.

Что вам нужно, так это последовательная философия языкового дизайна; язык должен уметь элегантно решать проблемы, для которых он предназначен. Модель для достижения этого может требовать или не требовать наследования, но трудно судить об этом без общей картины.

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

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

(Поиск примеров для двух изложенных парадигм оставлен в качестве упражнения для читателя.)

Итак, подумайте о том, какая семантика вам нужна, поиграйте с ними и посмотрите, достаточны ли они без наследования. Если это не так, вы можете либо решить добавить наследование в микс, либо решить, что чего-то еще не хватает.

tdammers
источник
9

Да.

Я думаю, что не разрешать наследование - это хорошо, особенно если ваш язык динамически типизирован. Вы можете добиться повторного использования аналогичного кода, например, путем делегирования или композиции. Например, я написал некоторые умеренно сложные программы - хорошо, не что сложно:) - в JavaScript без наследования. Я в основном использовал объекты как алгебраические структуры данных с некоторыми функциями, прикрепленными как методы. У меня также было несколько функций, которые не были методами, работающими с этими объектами.

Если у вас есть динамическая типизация - и я предполагаю, что у вас есть - вы также можете иметь полиморфизм без наследования. Кроме того, если вы разрешите добавлять произвольные методы к объектам во время выполнения, вам не понадобятся такие вещи, как миксины.

Другой вариант, который я считаю хорошим, - эмулировать JavaScript и использовать прототипы. Это проще, чем классы, но и очень гибкий. Просто что-то, чтобы рассмотреть.

Итак, все сказали, я бы пошел на это.

Тихон Джелвис
источник
2
Пока есть разумный способ выполнения задач, обычно выполняемых с наследованием, продолжайте. Вы можете попробовать несколько сценариев использования, чтобы убедиться (возможно, даже зайти так далеко, чтобы просить примеры проблем у других, чтобы избежать предвзятости ...)
прибывающая буря
Нет, это статично и строго напечатано, я упомяну это наверху.
Кристофер
Поскольку он статически типизирован, вы не можете просто добавлять случайные методы к объектам во время выполнения. Тем не менее, вы все еще можете делать утку: посмотрите протоколы Gosu для примера. Я использовал протоколы немного летом, и они были действительно полезны. У Gosu также есть «улучшения», которые являются способом добавления методов в классы после свершившегося факта. Вы также можете добавить что-нибудь подобное.
Тихон Джелвис
2
Пожалуйста, пожалуйста, пожалуйста, прекратите связывать наследование и повторное использование кода. Давно известно, что наследование действительно плохой инструмент для повторного использования кода.
Ян Худек
3
Наследование - это всего лишь один инструмент для повторного использования кода. Часто это очень хороший инструмент, а иногда это ужасно. Предпочтение композиции наследованию не означает, что никогда не следует использовать наследование вообще.
Майкл К
8

Да, это вполне разумное дизайнерское решение, исключающее наследование.

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

Например, Clojure не обеспечивает наследование реализации, предпочитая предоставлять набор ортогональных функций (протоколы, данные, функции, макросы), которые можно использовать для достижения тех же результатов, но гораздо более чисто.

Вот видео, которое я нашел очень поучительным по этой общей теме, где Рич Хикки определяет основные источники сложности в языках программирования (включая наследование) и представляет альтернативы для каждого: Простота - это легко

mikera
источник
Во многих случаях наследование интерфейса является более подходящим. Но при использовании с модерацией наследование при имплантации может позволить удалить много стандартного кода и является наиболее элегантным решением. Для этого нужны программисты, которые более умны и осторожны, чем обычные обезьяны Python / JavaScript.
Эрик Алапяя
4

Когда я впервые столкнулся с тем фактом, что классы VB6 не поддерживают наследование (только интерфейсы), это действительно раздражало меня (и до сих пор поддерживает).

Однако причина , по которой он так плох, в том, что у него также не было параметров конструктора, поэтому вы не могли сделать обычное внедрение зависимостей (DI). Если у вас есть DI, то это важнее, чем наследование, потому что вы можете следовать принципу предпочтения композиции перед наследованием. В любом случае, это лучший способ повторно использовать код.

Не имея Mixins, хотя? Если вы хотите реализовать интерфейс и делегировать всю работу этого интерфейса объекту, установленному посредством внедрения зависимостей, тогда Mixins идеальны. В противном случае вы должны написать весь стандартный код, чтобы делегировать каждый метод и / или свойство дочернему объекту. Я делаю это много (спасибо C #), и это одна вещь, которую я хотел бы не делать.

Скотт Уитлок
источник
Да, то, что вызвало этот вопрос, является реализацией SVG DOM, где повторное использование кода очень выгодно.
Кристофер
2

Чтобы не согласиться с другим ответом: нет, вы выбрасываете функции, когда они несовместимы с чем-то, что вы хотите больше. Java (и другие языки GC) отказались от явного управления памятью, потому что требовали большей безопасности типов. Хаскелл выбросил мутацию, потому что он хотел больше рациональных рассуждений и причудливых типов. Даже C отбросил (или объявил недопустимым) определенные виды псевдонимов и другое поведение, потому что он больше хотел оптимизации компилятора.

Итак, вопрос: что вы хотите больше, чем наследство?

Райан Калпеппер
источник
1
Только то, что явное управление памятью и безопасность типов абсолютно не связаны и определенно не несовместимы. То же самое относится к мутации и «причудливым типам». Я согласен с общим рассуждением, но ваши примеры довольно плохо подобраны.
Конрад Рудольф
@KonradRudolph, управление памятью и безопасность типов тесно связаны. A free/ deleteоперация дает вам возможность аннулировать ссылки; если ваша система типов не в состоянии отследить все затронутые ссылки, это делает язык небезопасным. В частности, ни C, ни C ++ не являются безопасными по типу. Это правда, что вы можете пойти на компромиссы в одном или другом (например, линейные типы или ограничения размещения), чтобы заставить их согласиться. Чтобы быть точным, я должен был сказать, что Java хотела безопасность типов с определенной, простой системой типов и неограниченным распределением .
Райан Калпеппер
Это зависит от того, как вы определяете безопасность типов. Например, если вы берете (вполне разумное) определение безопасности типов во время компиляции и хотите , чтобы целостность ссылок была частью вашей системы типов, то Java также не является безопасным по типу (поскольку она допускает nullссылки). Запрет freeявляется довольно произвольным дополнительным ограничением. Таким образом, то, является ли язык безопасным типом, зависит больше от вашего определения безопасности типов, чем от языка.
Конрад Рудольф
1
@Ryan: Указатели совершенно безопасны для типов. Они всегда ведут себя в соответствии с их определением. Это может быть не так, как вам нравится, но всегда в соответствии с тем, как они определены. Вы пытаетесь растянуть их, чтобы они стали кем-то, кем они не являются. Умные указатели могут достаточно просто гарантировать безопасность памяти в C ++.
DeadMG
@KonradRudolph: в Java каждая Tссылка ссылается на один nullили на расширение класса T; nullэто некрасиво, но операции по nullвыбрасыванию четко определенного исключения, а не искажения инварианта типа выше. Контраст с C ++: после вызова delete, T*указатель может указывать на память , которая больше не удерживает Tобъект. Хуже того, выполняя назначение поля с использованием этого указателя в назначении, вы можете полностью обновить поле объекта другого класса, если оно случайно оказалось размещенным по соседнему адресу. Это не безопасность типов по любому полезному определению термина.
Райан Калпеппер
1

Нет.

Если вы хотите удалить функцию базового языка, то вы повсеместно заявляете, что она никогда, никогда не нужна (или неоправданно трудна для реализации, что здесь не применимо). И «никогда» - сильное слово в разработке программного обеспечения. Вам понадобится очень веское обоснование, чтобы сделать такое заявление.

DeadMG
источник
8
Вряд ли это так: весь дизайн Java заключался в удалении таких функций, как перегрузка операторов, множественное наследование и ручное управление памятью. Кроме того, я считаю неправильным рассматривать любые языковые функции как «священные»; вместо того, чтобы оправдывать удаление функции, вы должны оправдать добавление ее - я не могу представить себе какие-либо функции, которые должен иметь каждый язык.
Тихон Джелвис
6
@TikhonJelvis: Вот почему Java - ужасный язык, и в нем нет ничего, кроме «ИСПОЛЬЗОВАНИЯ, СОБРАННОГО В ГАРБАЖЕ» - причина номер один . Я согласен с тем, что языковые возможности должны быть обоснованы, но это базовая языковая функция - она ​​имеет множество полезных приложений и не может быть воспроизведена программистом без нарушения DRY.
DeadMG
1
@DeadMG вау! Довольно сильный язык.
Кристофер
@DeadMG: я начал писать опровержение в качестве комментария, но затем я превратил его в ответ (см.).
Райан Калпеппер
1
«Совершенство достигается не тогда, когда нечего добавить, а когда нечего удалить» (q)
9000
1

Если говорить строго с точки зрения C ++, наследование полезно для двух основных целей:

  1. Это позволяет повторно использовать код
  2. В сочетании с переопределением в дочернем классе он позволяет использовать указатель базового класса для обработки объекта дочернего класса, не зная его типа.

Для пункта 1, если у вас есть какой-то способ поделиться кодом без чрезмерной акробатики, вы можете покончить с наследованием. Для пункта 2. Вы можете пойти по пути Java и настаивать на интерфейсе для реализации этой функции.

Преимущества удаления наследства

  1. предотвратить длинные иерархии и связанные с ними проблемы. Ваш механизм совместного использования кода должен быть достаточно хорош для этого.
  2. Избегайте изменений в родительских классах от нарушения дочерних классов.

Компромисс в значительной степени находится между «Не повторяй себя» и гибкостью с одной стороны и предотвращением проблем с другой. Лично я не хотел бы видеть наследование от C ++ просто потому, что какой-то другой разработчик может быть недостаточно умен, чтобы предвидеть проблемы.

DPD
источник
1

Как вы думаете, это нормально, чтобы не допустить наследования или Mixins? (учитывая, что пользователь по крайней мере будет иметь интерфейсы)

Вы можете сделать много очень полезной работы без наследования реализации или миксинов, но мне интересно, нужно ли вам иметь какое-то наследование интерфейса, т. Е. Объявление, которое говорит, что если объект реализует интерфейс A, то ему также необходим интерфейс B (т. Е. A является специализацией B и, таким образом, существует отношение типа). С другой стороны, ваш результирующий объект должен только записать, что он реализует оба интерфейса, так что там не так много сложностей. Все прекрасно выполнимо.

Отсутствие наследования реализации имеет один очевидный недостаток: вы не сможете создавать vtables с числовыми индексами для ваших классов, и поэтому вам придется выполнять поиск по хеш-функциям для каждого вызова метода (или придумывать хитрый способ их избежать). Это может быть болезненно, если через этот механизм вы направляете даже фундаментальные значения (например, числа). Даже очень хорошая реализация хэша может быть дорогой, если вы нажимаете ее несколько раз в каждом внутреннем цикле!

Donal Fellows
источник