Как стать автором
Обновить

Комментарии 396

НЛО прилетело и опубликовало эту надпись здесь
Реализация операций не зависит от ООП и в некоторых языках решается перегрузкой операторов.
Проблема тут не в реализации, а в том как обеспечить полиморфизм.
Наверное, все-таки разговор про иммутабельность?
В первом абзаце — нет, не про иммутабельность.
Тсс, а то Егор Бугаенко с адептами набегут!
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
тому яркий пример: objA.plus(objB) или objB.plus(objA)? (я в курсе про хелперы, но их существование не укладывается в классическую парадигму).

Не совсем понимаю где проблема.


std::string a("123");
std::string b("444");
const std::string a{"123123"};

std::string c{a+b};
std::string d{b+a};

a+=aa;

aa+="xxx"; // compiler throws an error here

По моему всё семантически чётко и ясно. Или всё-таки где-то проблемы?

>>const std::string a{«123123»};

должно быть
const std::string aa{«123123»};
Что делать, если мне часто нужно переворачивать строку?
Писать метод в функциональном стиле
std::string reverse(const std::string& src);

или в процедурном
void reverse(std::string& str);

Но в ООП это никак не заходит.

Угу опять вопрос мутабельности. Это холивар. А утилитарно всё зависит от цели:


  1. std::reverse() — swap
  2. std::string::rbegin() — реверсный итератор
  3. std::string::crbegin — константный реверсный итератор

Вы действительно считаете, что эта проблема "буриданова осла", кого-то остановит от желания программировать на C++ или в ООП парадигме в целом? Дать право программисту выбрать длительность жизненного цикла объекта и доступа к нему, или отобрать? Совсем! Чтоб неповадно было.


Вопрос выбора инструмента, дело каждого конкретного программиста. Всё это вкусовщина. Небо наземлю не упадёт если объекты будут mutable.


Вот вам гениальный пример для батхёрта (пусть стошнит каждого функциональщика):


class A {
  mutable std::mutex __mutex;
  std::string              __str;
public:
  void  set(const std::string& str)
  {
     std::lock_guard<std::mutex> sync(__mutex);
    __str=str;
  }
  const std::string get() const
  {
     std::lock_guard<std::mutex> sync(__mutex);
     return __str;
  }
};

Ух пронесло, — небо не упало!


Вы не поверите, люди до сих пор пишут на чистом C! Тут в ветке даже Дельфисты отметились.


Вам шашечки или ехать?

кого-то остановит от желания программировать на C++ или в ООП парадигме в целом?
На C++ можно программировать и в функциональном стиле.

Вам шашечки или ехать?
Я отвечал на
>> objA.plus(objB) не укладывается в классическую парадигму
> Не совсем понимаю где проблема

Что есть неудобства в ООП, когда объекты предоставлены из внешней (стандартной) библиотеки, и тут приходится съезжать на ПП или ФП.

Сам-то я не против ООП, но не отрицаю его проблемы.
Это типа extension methods, подсмотренные в c#?
Там же чёрным по белому написано: любую функцию можно вызвать как метод её первого аргумента.
Если это способ реализовать недостающие функции объектов, к коду которых нет доступа, то это просто костыль, синтаксический сахар ))

В сравнении с полноценным наследованием, тут недоступны protected-поля и методы, оставленные автором объекта для расширения его функциональности. Нельзя добавить новое поле, и т.п. ограничения.
То, о чём вы говорите — это уже monkey patching и от него больше вреда, чем пользы.

Любую мономорфную функцию в языке без подтипирования.
Все-таки при формулировке теоремы не стоит забывать про условия.

семантически чётко и ясно. Или всё-таки где-то проблемы?
Это потому что конкатенация предусмотрена создателями библиотеки std. Но описание перегрузки оператора умножения над двумя std::vector«double», чтобы считалось скалярное произведение, выходит за рамки ООП.

Вы наверное имели в виду STL. И что вам мешает следовать хорошему тону STL, и писать такие-же библиотеки. В STL кстати есть functional в котором в том числе ести и функциональный класс std::multiplies.


Самое главное, вас ведь никто не ограничивает писать то как вы хотите и как считаете нужным, главное что-бы вам удобно было. А зависать над вопросом "в какой парадигме мне это реализовать", — не продуктивно. Выбирайте хоть ФП, хоть ООП, хоть haskel хоть Java. И реализуйте это то как считаете верным. Какой смысл в войне между отвёрткой и гаечным ключём? Никакой! И тот и другой инструмент может понадобится.

НЛО прилетело и опубликовало эту надпись здесь
несколько операторов в одной строке [не нашел нужного научного термина, но думаю суть понятна]

Кортеж?
НЛО прилетело и опубликовало эту надпись здесь
Разумеется модифицировать существующий. Мембер принадлежит обьекту, значит и работает он с обьектом, какой то вопрос глупый, с чего бы вообще операции создавать новый обьект новый обьект создается конструктором, или фабрикой или мембером который явно определяет создание новое обьекта если мы говорим о колекции но никак операция над обьектом не должна создавать явно новый обьект оке мы можем создать мутабл переменную но в общем случае конечный программист использующий интерфейс обьекта об этом никогда не думает, ну и с каких пор обьектно ориентированное программирование использует plus? Это что за сумрачный гений это придумал, есть функции которые нативно выглядят как операторы языка, и вообще что плохого в перегрузке, то что она неявная оргамунт уровня add неявно — разумеется если вы используете класс не интегрального(фундаментального типа) с перегрузкой оператора вы ожидаете особого поведения сематически эквивалетного сложения к примеру для матриц или строк, снова какие то обсолютно выдуманные аргументы в пользу фп и иммутабельности, ооп никогда не топил за создание новго объекта при действиях типа сложения и операция на сематическую адекватность, крч, снова проблемы возникающие в результате отсутсвия вопроса самому себе — а что должна делать функция и кривых интерфейсах.

Вопрос не глупый. Например 1.Add(2) явно должен создавать новый объект 3, а не менять первый константный аргумент.

у вас равно — это присвоение или алиас? Потому что если мы смували `1` в `foo`, а потом изменением его, то изменятся все единицы во всей программе. Это явно не то, чего мы хотим.

Присвоение, разумеется. Изменится лишь локальная переменная.

В общем, вы не правы в общем случае. Метод может модифицировать объект, но не обязан. Куча апи на этом построена, например вот.
А вот подменять тезисы, пожалуйста, не надо.
Мембер принадлежит обьекту, значит и работает он с обьектом, какой то вопрос глупый, с чего бы вообще операции создавать новый обьект

Вопрос не глупый. Например 1.Add(2) явно должен создавать новый объект 3, а не менять первый константный аргумент.

Эмм, где подмена?
Логическое отрицание и антоним — разные вещи.

Реализуете интерфейс Add и все. Сигнатура этого метода явно должна сказать, что происходит. Например вот.


Инкапсуляция прекрасна, но, как любой инструмент, ей есть границы применимости. И то, что в современные ООП языки интенсивно проникают ФП элементы — свидетельство ограниченности классического ООП.

Инкапсуляция — это вообще свойство любой программы в любой парадигме. Вот написали вы let loadFromDatabase = loadFrom database — инкапсулировали загрузку из бд! Только вот ООП тут ни на гран.


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

Композиция и наследование ортогональны. Спросити у растовчан, насколько им приятно писать UI без наследования, услышите массу интересного.

Если, например, говорят про процедурное программирование, то говорят про него абсолютно спокойно

Чистое процедурное программирование — это треш, угар и содомия с логарифмическим графом зависимостей скорости разработки от количества кода. Говоря проще: Больше кода и больше проблем, где-то в некой точке X переходящей в уничтожение как личности каждого, кто попытается добавить туда ещё какую-нибудь процедурку/функционал.

Это самый очевидный подход для любого новичка и совершенно мёртвый в современном мире. Хотя есть и исключения, вроде такой известной штуки как Wordpress, которую пока не материл только ленивый.

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

P.S. А на ООП сваливаются все камни по той причине, что это некое «над» решение, которое успешно совмещает (в реальной жизни имеется ввиду, а не сферическом ваккууме) под своим крылом и функциональщину, и процедурщину, и декларативное, аспектное, контрактное, прототипное и прочие программирования. Это стержень архитектуры, а остальное — это лишь решение частных мелких проблем/алогритмов (ФП — это вообще ярковыраженный представитель таких решений, где для решения частной задачи надо понаклепать с десяток функций, которые потом композицией выстраиваются в красивую программку).
НЛО прилетело и опубликовало эту надпись здесь
В процедурном подходе никто не мешает вам иметь локальные состояния. Более того, некоторые рассматривают ООП в целом лишь как идею организации процедурного кода, а ООП возможности языков как синтаксический сахар, без которого можно обойтись и написать что-то вроде GTK в парадигме ООП но без сахара.
Напомните, на чём там написаны ядра linux, windows или freebsd?
Когда писались эти ядра, хорошо проработанной и реализованной концепции ООП еще не было. В результате получили такое чудо как Win API. Которое только Delphi смогло к более-менее удобоваримому и однообразному виду привести. Яркий пример, что может получится в результате использования ФП, к слову.
Странно приводить VCL как пример хорошей обёртки над WinAPI. VCL в Delphi ужасен и тянет груз совместимости с Win16. Было дело — использовал такую вещь как Watcom Optima — это тот же Delphi, только C++ и не отягощённый грузом совместимости — всё было гораздо проще, компактнее и по возможностям базовой библиотеки превосходило Delphi. (В своё время много использовал и Delphi и напрямую WinAPI и свои не-Delphi-йские компонентные системы над Win32 API делал и делал backports на Win16.)
Ну, первая версия ядра linux — 91 год, а C++ появился в 83. То есть времени для появления более-менее надёжных практик достаточно. А вот намного более поздняя HaikuOS уже в основном написана на C++ (кстати, BeAPI офигенная штука, намного удобнее WinAPI), хотя отдельные части всё ещё C.

Ещё пример: микроконтроллеры. Довольно мало любителей писать под них на плюсах. Ну, потому что ограничение ресурсов слишком жёсткое. Это уже не говоря про FPGA, где свой маленький мир.

И не забудьте про мои любимые шелл-скрипты, которые чуть менее, чем полностью состоят из процедурного подхода.

Я не говорю, что C++ и ООП плохо, просто есть места, где их использовать не стоит.
НЛО прилетело и опубликовало эту надпись здесь
Не знаю кода этих ОС, но знаю код некоторых других проектов на С, и он похож на ООП подход. Например, если рассмотреть модульность ffmpeg (как в libavcodec). В ОС, мне кажется, не обойтись без объектного мышления.
Чистое процедурное программирование — это треш, угар и содомия

Это самый очевидный подход для любого новичка и совершенно мёртвый в современном мире.

По-моему о процедурном подходе как раз и не говорят ничего плохого, т.к. это удел либо мелких одноразовых тулзовин

Расскажите это: Линусу Торвальдсу или создателям почти любой ОС, создателям apache, nginx, tarantul, и т.д.


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

Расскажите это: Линусу Торвальдсу или создателям почти любой ОС, создателям apache, nginx, tarantul, и т.д.
Вот только они не написаны в чистом процедурном стиле. То что в C нет классов не значит что в нем нельзя использовать ООП.
НЛО прилетело и опубликовало эту надпись здесь
Да, большинство современных проектов на C реализованны именно в рамках объектно-ориентированной парадигмы. В ручную:)
Одна из существенных проблем ООП, кмк, — реализация операций. То есть на классическую парадигму оно очень плохо ложится. Сложение чисел или конкатенация — тому яркий пример: objA.plus(objB) или objB.plus(objA)

Если перекладывать эту операцию на реальный мир, то всё зависит от того, что мы делаем. При наличии у нас двух коробок с апельсинами, мы можем переложить их из одной в другую, тем самым изменив состояния одной из коробок. При этом мы можем все апельсины переложить в третью коробку, тем самым создав новый объект (заодно выкидываем старые коробки). То есть всё зависит от конкретных требований.
Я вообще слабо представляю системы порядка единиц/десятков миллиона строк кода написанных в фп парадигме. Как написавший 2/3 от нескольких миллиннострочных проектов.
Также не совсем очевидны выгоды от иммутабельности. Возможно в каких-то случаях она полезна, сложно сказать. Как по мне — то проще найти кто меняет данные и разобраться с проблемами, чем запрещать мутабельность вообще. Может, конечно, языки такие, что это сделать сложно или невозможно. В Delphi это делается элементарно.
Тут ключевой вопрос не «кто может менять объект», а «когда его могут менять».

В частности, иммутабельный объект всегда потокобезопасен и реентерабелен без дополнительных усилий. В отличии от мутабельных объектов.
Тут есть какой нюанс. Иммутабельность довольно просто 'эмулируется' read only свойствами/инкапсуляцией. То есть — нужно — сделали иммутабельным. Не нужно — сделали обычным. А вот в тех языках, где всё иммутабельное выбора, увы, нет.
А вот в тех языках, где всё иммутабельное выбора, увы, нет.

Нужен ли такой выбор программисту, если компилятор потом сам приведет к оптимальному виду?
Таких компиляторов не существует, которые смогут понять, что код, написанный программистом, реализует иммутабельный словарь, и заменит его на гораздо более эффективный обычный мутабельный словарь, проанализирует сценарии конкурентного доступа и, где необходимо, защитит словарь mutex-ом.
Насколько мне известно:
1. Со строками компилятор С# действует подобным способом
2. В функциональных языках на уровне «для программиста» (как это делается в рантайме отдельный вопрос) иммутабельность реализуется путем создания копии, т.е. программисту в принципе нет необходимости реализовывать иммутабельный словарь
2. Вероятно, вы не знакомы с проблемой. Можно прочитать здесь, в первом пункте.
Кстати, вы по-моему путаете иммутабельность с потокобезопасностью… И я тоже про иммутабельный словарь повторил
Ну так иммутабельный словарь потокобезопасен.
Снег — белый, но не всё, что белое — снег
Также не совсем очевидны выгоды от иммутабельности. Возможно в каких-то случаях она полезна, сложно сказать.

Иммутабельность полезна в случаях, когда создаётся кусок данных (объект), который используется в нескольких местах и/или в разных потоках. Тогда вероятность того, что такой объект ВНЕЗАПНО поменяется, равна нулю. К слову, иммутабельность легко реализуется на любых ООП-языках (может, и не только ООП) — достаточно не писать сеттеров и не менять поля объекта кроме как в конструкторе. А вот наоборот — "чуть" сложнее.

Стандартный вопрос на собеседовании: «как реализовать неизменяемый объект?» и на ответ, данный вами
достаточно не писать сеттеров и не менять поля объекта кроме как в конструкторе
стандартный дополнительный вопрос: «а если я в конструктор передам изменяемый nestedObject? И потом изменю его состояние?». В результате беседы приходим к выводу, что вся иерархия объектов должна быть неизменяемой. И что вручную это сделать накладно, если в языке какой-нибудь Integer или String по своей природе mutable.
P.S. В основном я с вами согласен — неизменяемость полезна при многопоточности и может здорово упростить жизнь. Я лишь хочу подчеркнуть, что гарантии неизменяемости «из коробки» полезны. Особенно если есть четкая грань, что mutable а что нет. Например scala.mutable.Array vs scala.immutable.List — сразу понятно что к чему даже новичку в языке.
> «а если я в конструктор передам изменяемый nestedObject? И потом изменю его состояние?».

То ничего не изменится — наш объект останется иммутабельным (если забыть про рефлексию и подобные трюки). :)
Я придрался к фразе
иммутабельность легко реализуется на любых ООП-языках (может, и не только ООП) — достаточно не писать сеттеров и не менять поля объекта кроме как в конструкторе
В вашем примере неизменяемость реализована за счет ключевого слова immutable — такое есть не во всех языках.
Вот код на java, показывающий идею
class Compensation {
	int salary;
	int yearPremium;
	public int getSalary() {return salary;}
	public int getYearPremium() {return yearPremium;}
}

class Employee {
	private Compensation compensation;

	public Employee(Compensation compensation) {
		this.compensation = compensation;
	}

	public int calculateYearCost() {
		return compensation.getYearPremium() + 12 * compensation.getSalary();
	}
}

@Test
public void testImmutable() {
	Compensation offer = new Compensation();
	offer.yearPremium = 1000;
	offer.salary = 1000;
	Employee newcomer = new Employee(offer);
	offer.yearPremium = 1_000_000;
	assertEquals(newcomer.calculateYearCost(), 13_000);
}

Злобный хакер баг-хантер получит премию, а на собеседовании стоит внимательно слушать вопросы ;-)
Главный минус и главный же плюс использования иммутабельности в хорошо знакомых мне ООП языках — сравнение по ссылке говорит изменялась ли сущность, но не говорит, что, возможно, состояние вернулось в начальное после нескольких изменений и никак реагировать на них не нужно.
Для этого, если мне не изменяет память, существует шаблон Publish-Subscribe, чтобы ООП-парадигма в отдельно взятой реализации не превратилась в Eiffel.
НЛО прилетело и опубликовало эту надпись здесь
Не понял о каком времени речь.
НЛО прилетело и опубликовало эту надпись здесь
В каком времени? В реальном или моделируемом?
НЛО прилетело и опубликовало эту надпись здесь
Чем дальше, тем меньше понимаю о чём речь.
НЛО прилетело и опубликовало эту надпись здесь
Как-то вы ВНЕЗАПНО перескочили к безопасности сервера и небезопасности клиента, хотя ничего, НИЧЕГО! Не предвещало этого. Я даже капсить начал, чтобы обозначить, насколько внезапный и неуместный, слово детский понос в метро, был ваш комментарий.
Вы упустили один важный момент в сравнении наследования и композиции — наследование оптимизирует память (меньше объектов) и скорость (меньше cpu циклов на создание а потом и уборку объектов). Когда имеется например 10 последовательных переопределений поведения или добавлений нового функционала (для ui-разработки компонентов это распространенная ситуация) то для какого-нибудь списка из тысячи компонентов при использовании композиции будет создано 10 тысяч объектов а при использовании наследования в десять раз меньше — 1 тысяча объектов. А если будет цепочка из 20-ти переопределений или расширений то используя композицию будет создано уже в 20 раз больше объектов и т.д — то есть сколько бы цепочек не городили бы с наследованием количество объектов нисколько не увеличивается в отличие от композиции
Это касается только языков, которые не умеют выделять память под композицию объектов как одно целое. Например, в C++ не никакой разницы, делать
class InputDevice : public Device { ... }
или
class InputDevice {
   public: Device device;
}

Вы имеете ввиду аллокацию на стеке? А нет ли в с++ оверхеда по памяти на сам объект класса? То есть если внутри класса Device будет еще одно поле


class Device {
   public: AnotherDevice device;
}

А внутри AnotherDevice еще одно поле с инстансом другого класса и т.д, и если будет такая цепочка из тысячи вложенных полей вырастет ли итоговый размер объекта InputDevice в сравнении вариантом когда строим цепочки через наследование или нет?

С++ аллоцирует память под композицию 1 раз, независимо от того, объект на стеке создан или в куче. В структуре объекта поле public: AnotherDevice device;
это не указатель, это сам объект AnotherDevice, все его поля. Компилятор знает, с какого смещения начинается вложенный объект и формирует указатели на вложенный объект, когда они нужны, добавлением константы к указателю на объект-композит.
А в с++ размер объекта будет строго равен сумме размеров объектов его полей или добавится еще небольшой оверхед на какие-то служебные поля? Получается что если есть даже минимальный оверхед на объект в один бит то с ростом цепочки вложенности будет расти и размер главного объекта а с наследованием длина цепочки переопределений на размер объекта не повлияет.
Сумма полей. К таковым, в данном случае, относятся укзатели на виртуальные функции — тоесть размер всех переменных-членов класса + vtable, а далеехоть так хоть этак определяй InputDevice — результат будет одинаковый.
Без виртуальных методов будет строго одинаково.
Для каждого включенного объекта с вирт. методами будет на 1 указатель больше (vptr).

Однако, если не наследоваться, виртуальные методы не нужны :D
НЛО прилетело и опубликовало эту надпись здесь
У меня есть своя теория на этот счет. В программировании полно народу, которые пришли сюда не потому что им это нравится, а потому что тут бабки платят. Соответственно среди таких полно даже гуманитариев. А гуманитарии не могут в абстракции, так как это по сути уже математика, уравнение в котором неизвестным является сразу все.
НЛО прилетело и опубликовало эту надпись здесь
Верстальщик это по сути junior JavaScript developer. Если нет, то верстальщик становится невостребованным на рынке. Гуманитариям приходится плакать, но продолжать жрать кактус. Плач выражается в хейте ООП.
Ковырните соседей по офису, запросто может обнаружится, что тот офигенный спец, который строит офигенный продукт, в девичестве имеет красный диплом по, скажем, экономике.
Не встречал таких. Физиков, математиков и конструкторов — встречал, экономистов — максимум тестеры или верстальщики (без особого JS), а вот программистов, не умеющих в «ться», встречаю постоянно.
НЛО прилетело и опубликовало эту надпись здесь
Видел, правда ещё не разобрался к какому классу относятся эти товарищи.При мне работали Барышни-погромистки в великой желтой программе, все сплошняком гуманитарии. И да, они таки писали.
А вы этой теорией за чей лагерь? «За ООП» или «против ООП»?)
Я за то, что все хорошо в меру.
Как то раз делал тестовое задание при приеме на работу. Нужно было написать демона, который пингует адрес и шлет репорты, если пинг не проходит. Там кода 10 строчек. Ну я и написал просто 10 строчек. Понятно, что для демонстрации скиллов этого не достаточно и я прикрутил демону ООПешную возможность прикручивать разные способы отправки репортов. Но все равно первые же два вопроса были «Почему не весь код ООП?», «Почему не использовали фреймворков?». Прикручивать какойнибудь симфони ради 10 строчек кода, да еще и демону?.. На работу меня тогда не взяли…

И хорошо что не взяли, Вам же лучше, вы просто не застряли в болоте. Когда парадигма, — самоцель и когда годами размышляют над иерархией классов и тремя шаблонами, или над мутабельностью/иммутабельностью, — ничего хорошего из этого не выходит.


Вообще это такой суровый ынтырпрайз, не написал 2000 строк и всё через ООП вместо десяти которыми можно решить задачу. При этом перед тем как писать код ещё и UML диаграммы все нарисовать от use-case и заканчивая deployment. Это как дрескод нарушуть и в плавках на банкет явиться.

НЛО прилетело и опубликовало эту надпись здесь
  1. ООП смешивает данные и действия над ними. Это плохо

Думаю, Вы не совсем правильно поняли этот аргумент. Плохо когда пытаются абсолютно всё представлять объектами, искусственно симулируя наборы данных и действий там, где их нет: число 3 это не объект, равно как и функция sum(a,b int) не является частью/методом какого-то объекта. Всё хорошо в меру, где-то используемые кодом абстракции действительно лучше представлять в виде объектов, но требование чтобы объектом было абсолютно всё добавляет accidental complexity на пустом месте.


  1. Наследование закрепощает программу, делает трудным внесение изменений

Настоящее отношение is a между двумя разными абстракциями в реальном коде встречается крайне редко. А когда его пытаются насильно внедрить ради формального следования DRY случается беда. Поэтому наследование оказалось далеко не таким полезным инструментом, как его нам пытались п(р)одать евангелисты ООП. Из-за этого было написано очень много кода, которое использовало наследование слишком интенсивно и почти всегда не к месту, что и вызвало проблемы. Да, инструмент не виноват, но конкретно этот оказался настолько слабо применимым и проблемным, что ему место на дальней полке, и, по хорошему, разрешать им пользоваться можно только очень квалифицированным разработчикам.


  1. Методология ООП изначально ошибочна
    Абсолютно необоснованный аргумент. ООП создавалось для того, чтобы моделировать своеобразный виртуальный мир, состоящий из объектов, как и наш мир.

Наш мир не состоит из объектов. Это просто один из способов смотреть на мир, и способ довольно корявый и ограниченный. Иными словами эта абстракция не очень корректна, поэтому попытки применять её часто дают не очень хорошие результаты. А поскольку эта идея подаётся как основание ООП… Ничего не имею против ООП, но вот этот бред про "мир состоит из объектов" надо забыть как можно быстрее, он больше вредит ООП чем помогает.


  1. Но даже миллионы мух не убедят нас, что навоз — это вкусно
    Мол, массы в большинстве своём глупы (всё же я не думаю, что это относится и к программистам), бегают по «модным шмоткам» и восхищаются ими.

Программисты, в первую очередь, тоже люди. И многие действительно ведутся на модные технологии. Не потому, что они глупы, а потому, что просто быть умным недостаточно. Здесь на хабре иногда попадаются вполне умные разработчики, которые часто смотрят телевизор, и верят в то, что там рассказывают. Одного ума мало, чтобы противостоять давлению рекламы, моды и политтехнологий. К сожалению.

Плохо когда пытаются абсолютно всё представлять объектами, искусственно симулируя наборы данных и действий там, где их нет

Это называется ООП головного мозга. Все хорошо в меру. Есть даже такой софистический прием — довести идею до обсурда, а потом на основе этого доказывать неверность идеи.
А когда его пытаются насильно внедрить ради формального следования DRY случается беда.

Тут даже из вашего примера следует, что проблема не в ООП, а в том что у когото DRY головного мозга. До меня однажды докапались, почему у меня дважды написаны 4 одинаковые строчки, мол я должен вынести все в функцию. Только вот каждая из этих строчек уже была функцией, но с разными параметрами. Вынесение в функцию вылилось бы в усложнение кода и даже в увеличение количества строк. Но человеку было все равно, донт репит ёрселф и все тут.
Наследование — это самый мощный инструмент ООП. По-сути, без наследования (и связанного с ним полиморфизма) ООП особого смысла не имеет, ибо все остальное можно смоделировать, назвав процедуры удобным тебе образом.

Тут надо отойти в сторонку и сказать, зачем нужны ООП/ФП/etc. Основная цель любой методологии и любого нормального языка программирования — упростить решение задач. Определенных задач, разумеется, потому что универсального подхода пока никто не придумал. В программировании простота решения коррелирует с количеством кода, которое ты должен написать в прикладной программе. Пишешь меньше кода — тратишь меньше времени, упрощаешь поддержку продукта. Именно с этой точки зрения надо смотреть на языки программирования. Категории «труъ» и «не-труъ» интересны только как индикаторы достижения цели.

Так вот, для упрощения работы программиста в незапамятные времена были придуманы библиотеки процедур и вообще структурное программирование. Сейчас апологеты композиции (composition over inheritance) говорят, что вам достаточно скомбинировать библиотечные функции нужным образом чтобы получить требуемое сложное поведение. Это так, но таковая композиция сама по себе является сложной сущностью. Аналог такой сущности — стойка с сетевым оборудованием. Все компоненты в стойке стандартны, но конкретная конфигурация стойки — нет.

Что делать, если нам нужно переиспользовать такую композицию? В функциональном подходе можно сделать функцию, которая будет собирать составляющие ее функции нужным образом. Это отлично работает до тех пор, пока нам не потребуеся подменить одну фукцию из частей композиции на другую. Как это сделать без копирования всей композиции и замены одного имени на другой? Передавать этот подменяемый кусочек в параметрах? Неудобно, ибо в композиции может быть большое количество заменяемых частей, и нам придется предавать целую кучу параметров.

Класс в ООП — это по сути композиция функций, в которой можно подменять отдельные компоненты. Для этого там существует наследование и полиморфизм. Все просто.

По-сути, без наследования (и связанного с ним полиморфизма) ООП особого смысла не имеет

Полиморфизм — безусловно правильная штука. Только вот к наследованию его привязали именно в ООП, а вообще полиморфизм отлично существует и без наследования. В качестве примера посмотрите на интерфейсы в Go или просто почитайте статью в википедии про варианты полиморфизма.

Почитаю, спасибо, но речь же шла не о преимуществах Go перед другими языками. Речь о том, что, имея поддержку ООП в своем повседневном языке, человек ей не пользуется. По надуманным причинам.

Ещё точнее — речь шла о наследовании как инструменте, а не о полиморфизме. Если наследование применяется не как инструмент для создания реальной иерархии, DRY и моделирования отношений is a между сущностями предметной области, а просто как синтаксическая форма записи необходимая для использования полиморфизма в конкретном языке (т.е. речь о создании абстрактных классов нужных исключительно для декларирования набора поддерживаемых методов, от которых потом выполняется множественное наследование не вкладывая в это никакого смысла кроме "я поддерживаю этот набор методов и подразумевающуюся с ним семантику") — в этом ничего плохого нет (как впрочем в этом нет и настоящего наследования чего либо).

Ну это просто одна из проблем, которую хорошо решают наследование+полиморфизм. Есть и другие, в том числе включающие наследование не только поведения, но и данных. Но в качестве контраргумента было достаточно одного примера.
НЛО прилетело и опубликовало эту надпись здесь
Наш мир не состоит из объектов.

Совершенно очевидно, что мир состоит из иммутабельных структур, а где-то рядом витают модули, в которых есть функции, позволяющие взять, например, иммутабельное яблоко и создать на его основе такое же, только без кожуры.
Совершенно очевидно, что мир состоит из иммутабельных структур
Назовете хотя бы одну иммутабильную структуру в реальном мире, не опускаясь до уровня элементарных частиц?

В примере с яблоком никакого нового объекта не создается, иначе достаточно было бы потыкать в яблоко зубочисткой, чтобы накормить всех голодающих в Африке.
Кажется, у вас детектор сарказма сломался…
Ну так тэга то не было, а по косвенным признакам не сработал — бывает…
Сарказм и отсылка к парадоксу Тесея
число 3 это не объект, равно как и функция sum(a,b int) не является частью/методом какого-то объекта.


А что такое число 3? Это «данные»? А что такое «данные»? Как «данные» могут существовать без интерпретатора?

Если я напишу вам сообщение «На столе три яблока», или «На столе 3 яблока», или «На столе III яблока», скорее всего, во всех трех случая вы представите себе подобное:


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

Объект — упаковка для смысла. Методы объекта — способ понимания смысла.
"Код на Smalltalk"

tableWithThreeApples := me interpret: 'На столе три яблока'

apple := tableWithThreeApples takeOneApple.
apple isFruit. "true"

numberOfApples := tableWithThreeApple howManyApplesDoYouHave.
numberOfApples = 2. "true"


Но если интерпретаторы разные, то и объекты, а значит и смысл могут быть разными. Вполне возможен такой поворот событий:
tableWithThreeApples := appleFanboy interpret: 'На столе три яблока'

apple := tableWithThreeApples takeOneApple.
apple isFruit. "false"
apple isComputer. "true"

numberOfApples := tableWithThreeApple howManyApplesDoYouHave.
numberOfApples = 2. "true"


Возвращаясь к числу 3, невозможно понять его смысл, пока нет объекта, который этот смысл упаковывает, и методов, которые этот смысл поясняют.

Читая, хотелось аплодировать стоя — совершенно волшебная демагогия.


Возвращаясь к числу 3, невозможно понять его смысл, пока нет объекта, который этот смысл упаковывает, и методов, которые этот смысл поясняют.

Расскажите это на уроке математики.

Ребенку, который не знает чисел и основ математики, будет непонятно число 3. Для такого ребенка в числе 3 нет смысла. Он не сможет интерпретировать сообщение «3 + 2», потому что не знает смысл объектов «3» и «2» и доступных у них методов вроде "+" и "-".

Хоть вы и не ответили на мои вопросы из прошлого сообщения, я все рано задам еще. Вы действительно думаете, что числа — это что-то не имеющее смысла? А если смысл всё-таки есть, то разве методы (операции, возможности) вроде "+", "-", "*", "/" существуют не для пояснения смысла, заключенного в число?
А если смысл всё-таки есть, то разве методы (операции, возможности) вроде "+", "-", "*", "/" существуют не для пояснения смысла, заключенного в число?

Только вы все напутали. +-*/ — это как раз объекты, а вот у них уже в свою очередь есть метод apply, который принимает в качестве аргументов числа: plus.apply(1,2)

Вопрос трактовки. Формально математические операции — это функции, а не объкты. Объекты имеют состояние, функции — нет. то есть не plus.apply(1,2), а plus(1,2). Или this = 1; plus(this, 2), для чего есть запись 1.plus(2) :)
Объекты имеют состояние

Могут иметь. А могут — не иметь.


Или this = 1; plus(this, 2), для чего есть запись 1.plus(2) :)

Тогда у каждого числа свой plus, что семантически неверно, операция-то только одна.

Тогда у каждого числа свой plus

С чего бы? Методы как бы общие для всего класса чисел.


Но дело-то не в них. Существует множество возможностей представить предметную область в виде набора объектов, и ни один из них не является заведомо правильным и семантичным.


Простейший такой способ — каждое число как объект. Другой способ — операция как объект.


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

С чего бы? Методы как бы общие для всего класса чисел.

Нет, в ООП вообще у каждого объекта свой метод. Просто часто они совпадают.


Первый случай больше подходит для создания собственных алгебр

Первый вообще ни в каких случаях не подходит.

В ООП вообще нет методов, с одной стороны, с другой, в тех реализациях, где они есть, чаще всего все методы одного класса делятся друг с другом, только значение указателя/ссылки типа this/self разное при вызове метода объекта, а код один.
В ООП вообще нет методов, с одной стороны

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


чаще всего все методы одного класса

Методы не у класса, а у объекта. ООП ортогонально классам (на то оно и объектно-ориентированное, а не классо-ориентированное :)).
ООП с классами — это лишь один из вариантов.

Скорее методы очень частный случай сообщений, и это лишь один из вариантов :)
Скорее методы очень частный случай сообщений

Это не частный случай, а просто разные названия одного и того же :)

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

Ну и плюс собственно посылку сообщения можно реализовать через вызов метода.
Ну и плюс собственно посылку сообщения можно реализовать через вызов метода.

И наоборот. Именно по-этому это и есть одно и то же.

Везде есть функции. А вот диспетчеризации их вызовов могут быть разные. Это может быть прямой вызов конкретной функции (в том числе невиртуальные методы). Может быть выбор наиболее подходящей функции в зависимости от типа объекта (виртуальный метод), может быть выбор на основе всей сигнатуры (множественная диспетчеризация). Посылка сообщения — это вызов функции приёма сообщения с параметрами: объкт и сообщение. А как она диспетчеризируется зависит от языка.

Хорошо, «уговорили», у каждого объекта свой метод.

Но чем это плохо? Почему одной операции не может соответствовать целый класс методов?
Почему одной операции не может соответствовать целый класс методов?

А что это значит с точки зрения семантики? Мы же моделируем предметную область, так?

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

Ну так операции над числами не принадлежат числам. Они не являются ни событиями, ни реакцией на эти события.

Это лишь условность. Захотим чтобы принадлежали — будут принадлежать.
Это лишь условность. Захотим чтобы принадлежали — будут принадлежать.

Так я же и спрашиваю — с точки зрения семантики предметной области это какой смысл имеет?
Так-то мы можем куда угодно засунуть какой угодно метод, например почему бы арифметическим операциям не быть методами уток? Или плюс — а утки, а умножение — у жирафа?
Надо что-то умножить? Отлично! Создаете жирафа с нужной шеей при помощи фабрики жирафов и умножаете! :)
Бессмысленно? Не более, чем иметь операцию плюс у числа.

Простите, а чем вас "сложи себя вот с этим числом и сообщи результат" в качестве семантики не устраивает-то?

а чем вас "сложи себя вот с этим числом и сообщи результат"

Тем, что числа не умеют складывать :)
Числа являются объектами арифметических операций, а не субъектами.

Умеют они себя складывать или нет — зависит только от построенной модели.
Умеют они себя складывать или нет — зависит только от построенной модели.

Так я ж говорю — ради бога, можете хоть жирафами умножать. Только это шизофрения, а так-то — норм все.

Если подразумевать под числом точку на прямой, то сложение это сдвиг заданной точки на заданный вектор. Это больше подходит для оператора '+=', но тем не менее смысл есть.
Зависит от того как моделируем. Сложение можно представить как сообщение числу «я хочу увеличить тебя на вот это число», а результат как «да, я себя увеличил, теперь у меня новое состояние».
Сложение можно представить как сообщение числу «я хочу увеличить тебя на вот это число», а результат как «да, я себя увеличил, теперь у меня новое состояние».

"Состояние у числа" — это уже совсем что-то шизофреническое, уж извините.

Только в рамках классической математики может выглядеть странно. Модель программы вовсе не должна следовать общепринятым математическим и прочим моделям.
Только в рамках классической математики может выглядеть странно.

Замечательно, я не против того, чтобы кто-то сделал свою неклассическую математику, но только тогда ваши числа — не числа.
А то, что для не-чисел может быть все не так как для чисел — это, конечно, верно.


Модель программы вовсе не должна следовать общепринятым математическим и прочим моделям.

Да, модель не обязана следовать чему бы то ни было, но обычно такая модель — говно, а не модель.

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

Попробуйте отказаться от такого:
i=i+1
Попробуйте отказаться от такого:

  1. зачем?
  2. что тут чему противоречит?
зачем?

Потому что такая запись противоречит общепринятым математическим моделям, где изменение значения принято обозначать другой переменной (Ti, Ti+1, Ti+2 и т.д., вместо T, T, T).
что тут чему противоречит?
из i=i+1 следует противоречие 0=1
Потому что такая запись противоречит общепринятым математическим моделям

Почему противоречит? Нет.


из i=i+1 следует противоречие 0=1

Нет, не следует, потому что = — это присваивание, а не сравнение.

Но обозначается тем же символом. Выражаясь вашими словами — шизофрения это получается.
Но обозначается тем же символом.

Какая разница, каким символом оно обозначается? Важно не как вы что обозначаете, а какой эти символы несут смысл.
Шизофрения — это когда у вас что-то, что по смыслу должно быть числом, не ведет себя как число.
Обозначать же вы можете что угодно как угодно, и то, что в разных языках одни и те же вещи могут обозначаться по-разному (а формальный язык ZFC и язык какого-нибудь ЯП — это разные языки) не является чем-то странным.

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

Значение переменной не меняется. Иначе это не переменная, а функция, либо последовательность.
Вместо присваиваний в математике есть введение обозначений (аналог let в функциональных языках).

И что дальше? Вещи-то это разные. Где противоречие?


Значение переменной не меняется.

Вы же понимаете, что "переменная" в языке программирования и "переменная" в математике — это два совершенно разных объекта, которые просто называются одинаково?
Не надо подменять понятия и тогда никаких проблем у вас не будет. А иначе — да, повсюду одни противоречия начнутся. Если одним и тем же словом называть разные вещи и из этого пытаться какие-то следствия выводить.

Я просто вас процитирую
Модель программы вовсе не должна следовать общепринятым математическим и прочим моделям.
Да, модель не обязана следовать чему бы то ни было, но обычно такая модель — говно, а не модель.

В какой математике вы видели общепринятую модель с изменяющимися во времени переменными?
В какой математике вы видели общепринятую модель с изменяющимися во времени переменными?

Да в любой, которая описывает операционную семантику императивного языка.

А конкретнее? Чтобы была построена формальная система? Я знаю только одну модель, описывающую эволюцию переменных в памяти — машина тьюринга.
А конкретнее? Чтобы была построена формальная система?

Речь о семантике для языка.
https://ru.wikipedia.org/wiki/%D0%A1%D0%B5%D0%BC%D0%B0%D0%BD%D1%82%D0%B8%D0%BA%D0%B0_(%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5)

Вы читали текст по ссылке? Он не даёт требуемых примеров:
В качестве инструментов построения таких моделей могут использоваться различные средства, например, математическая логика, λ-исчисление, теория множеств, теория категорий, теория моделей, универсальная алгебра.

И где в λ-исчислении (и т.д. по списку) модель императивного языка?

Вы б дальше текст по ссылке изволили прочесть, там где виды семантик. После этого ваши вопросы должны отпасть. В частности, нас особенно операционные интересуют:
https://en.wikipedia.org/wiki/Operational_semantics

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

И? :)

Хорошая модель строится не по совокупности всех доступных знаний человечества, а по знаниям предметной области в данном в задаче контексте. Пример, спроси у бухгалтера что такое налоговая ставка, он скажет что это число, которое..., сейчас по умолчанию оно равно N. А потом скажет, что в некоторых случаях это число увеличивается до M.
Пример, спроси у бухгалтера что такое налоговая ставка, он скажет что это число, которое...

Не важно, что он скажет, важно, что он будет иметь ввиду. Мы ведь люди и общаемся на ЕЯ. Если вы будете такие слова бухгалтера буквально понимать, у вас, очевидно, проблемы.
В данном конкретном случае — я не думаю, что вы будете для decimal делать метод "увеличить_ставку".

Для Decimal наверное не буду, а вот для TaxRate extends Decimal сделаю метод «increase» именно для поддержки ЕЯ в предметно области

Но ведь это будет метод именно TaxRate, а не метод "числа" :)
Видите, вы верно поняли бухгалтера (не смотря на то, что "формально" он вроде как дичь сказал) и все в порядке.

TaxRate является числом. Грубо, есть натуральные числа, целые, дробные, десятичные и есть налоговые ставки. Ну а если бухгалтер про каждое число в программе будет так говорить то добавляю метод в Decimal, ЕЯ же.
Как по мне — композиция тут больше подходит. А вообще спор у вас странный, я потерял мысль( Можете, пожалуйста, прояснить предмет?
Для демонстрации того, что число может являться объектом со своим состоянием и методами, его изменяющими, наследование подходит лучше :) Собственно спор о том, является ли шизофренией представление числа в виде объекта, в том числе мутабельного.
А какая разница? Вот допустим, что не является. Или допустим, что является. Если одна из точек зрения верна, то следствие...?
Если моя точка зрения неверна, то можно ставить диагнозы по коду. В частности авторам всех языков, где всё объект или хотя бы есть объекты, представляющие числа. Ну и держаться от такого кода подальше, чтобы не заразиться шизофреническим мышлением :)
Ну а до того? Если подняться на уровень выше. Ну вот допустим ваш оппонент погарячился и не совсем так выразился и именно этот пример не так важен, но тот, изначальный родник, который привел нас к этой реке — о чем он?
Я так понимаю, речь про то, что Druu имеет ввиду числа как математические числа. То есть вечные константные инстансы, на которые ссылаются все математики мира. Тогда «изменение числа» это запись вида «2 := 10», что действительно странно.
Я так понимаю, речь про то, что Druu имеет ввиду числа как математические числа.

Правильно. Если же ваши числа — не математические, то это будет указано либо в семантике языка, либо будет что-то в виде TaxeRate из примера выше.

А почему для этой задачи не использовать персептрон? Он лучше, на ваш взгляд, справится?

Угу, но если я задам вопрос: "вы добавили метод к decimal?", то вы ведь ответите: "нет!", правильно? :)

НЛО прилетело и опубликовало эту надпись здесь

Более того, предлагаю подумать над такой темой, как сложение разных типов: 1 + 1.1 — метод какого класса должен быть вызван: левого операнда или правого? А если это должна быть внешняя функция, то как быть в случае сокрытия внутреннего состояния операндов? Можно попробовать воспользоваться более широкой областью видимости, но как быть в случае, если каждый класс раз разрабатывается совершенно разными людьми? Кто из них должен обеспечить совместимость с другими типами?

Вы действительно думаете, что числа — это что-то не имеющее смысла?

Нет, я такого не говорил. Я говорил, что число — это не объект. Смысл, как и методы, к числу 3 можно прилепить самые разнообразные (это вопрос упомянутой Вами интерпретации). А можно этого и не делать. Число 3 от такого отношения к нему не испортится, не сломается, не обидится, и не станет менее полезным. Это и отличает данные от объектов: у объекта есть "смысл"/интерпретация, пользуясь Вашей терминологией, а у данных этого нет. Да, к данным это можно добавить, и мы получим объект. Но делать это вовсе не обязательно.


А если смысл всё-таки есть, то разве методы (операции, возможности) вроде "+", "-", "*", "/" существуют не для пояснения смысла, заключенного в число?

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

Как только число 3 превращают в объект, к нему гвоздями прибивают какой-то один смысл

оно ограничивает наши возможности по интерпретированию числа 3 каким-то иным образом


Вам ничего не мешает добавлять любой смысл к объекту. Если в вашей ментальной модели число может быть кодом символа, отобразите это в числе:
charCode := 3 asCharCode.
charCode + 1. "Instance of CharCode didNotUnderstand #+"


Это и отличает данные от объектов: у объекта есть «смысл»/интерпретация, пользуясь Вашей терминологией, а у данных этого нет.

Пока это число 3, это просто данные, и мы можем наделять их любым смыслом


То есть данные — это все-таки что-то, что не имеет никакого смысла? Если число «3» и строка «лето» являются данными, значит ли это, что они по-сути равны как друг другу, так и абстрактному «ничего»?
Начали за здравие, закончили за упокой.

Из наличия собственного смысла у «трех яблок» никак не следует отсутствие собственного смысла у числа «3».
Конечно у числа есть собственный смысл, я это не отрицал. Я пытался донести идею о том, что числа — это не «данные», числа — это представление определенного смысла. А компактное представление смысла с возможностью исследовать и понимать смысл через использование доступных методов (операций, возможностей) — это объект.

Предлагаю отвлечься от программирования и подумать о том, как объяснить смысл чисел, например, дельфину.
Числа — данные, объекты — данные и методы их преобразования. Смыслом, как конкретным, так и абстрактным, и числа, и объекты наделяет человек. Дельфины вроде тоже что-то могут. :)
В первом сообщении я задавался вопросом, на который пока что никто не ответил :(
А что такое «данные»? Как «данные» могут существовать без интерпретатора?


Смыслом, как конкретным, так и абстрактным, и числа, и объекты наделяет человек.

Как сделать так, чтобы можно было передать смысл одного и того же объекта без искажений?
Как сделать так, чтобы можно было передать смысл одного и того же объекта без искажений?

Записать его определение.

В общем случае задача передачи смысла без искажений не имеет решения :( Каждый субъект передачи может иметь свои интерпретации смысла одних и тех же данных. Можно стремиться к тому, чтобы уменьшить различия, но полностью их устранить вряд ли получится.

С другой стороны, не всегда участников передачи интересует полное понимание смысла, достаточно чтобы соблюдались «правила игры».
А компактное представление смысла с возможностью исследовать и понимать смысл через использование доступных методов (операций, возможностей) — это объект.

У натурального числа только два доступных метода — это next() и isZero()

НЛО прилетело и опубликовало эту надпись здесь
Что характерно, например, в разработке веб-фронтенда такие хейтеры ООП спокойно используют HTML DOM и не жалуются, что это не кошерно. Возможно, даже не обращают внимание на факт соприкосновения с объектами.
Автор поднял правильную тему, и в правильном ключе. Спасибо.
Я, например, жалуюсь. DOM, как и HTML — то еще… чудо. HTML то нарушает еще одну известную парадигму, смешивая код (js) с интерфейсом (всё остальное). Но, другого просто нет, увы.
А вы не смешивайте, вот и все.
По моим впечатлениям, самые полярные и непримиримые мнения по функциональщине.
Плюсую, про ООП, кмк, все давно смирились с неизбежным
КМК эти мнения ничего не значат, функциональщина это чистый матан, кто с матаном не дружит, тот и функциональщину не осилит, процедуры и ООП понять проще, уровень абстракции меньше.
По моим впечатлениям, самые полярные и непримиримые мнения по функциональщине.

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

Другая проблема, что сейчас много людей говнокодят на JS в процедурном стиле с элементами ФП и топят, что они — функциональщики, потому что отказались от классов.
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Я не автор, но отношусь к этому сугубо отрицательно. Говорят, множественное наследование сложно, потому что может возникнуть «deadly diamond of death», но эту проблему можно элементарно решить на уровне компилятора. Если в классе возникает проблема неоднозначности выбора метода, то нужно заставить разработчика явно реализовать этот метод. Вот и все, вроде.

А какую проблему по вашему должно решать множественное наследование? От обычного-то обычно предлагается отказаться в пользу композиции, про множественное я и молчу. Если у вас объект и швец, и жнец, и игрец, то это уже полное нарушение SRP.

Представлять одну сущность с разных сторон. Яблоко, например, и фрукт, и шарообразное тело. Или квадрат — и ромб, и прямоугольник )))
Нет, примеров из реальности я могу привести. А вот примера из программистской жизни — не очень. Пару раз когда подобное требовалось это было вызвано дико кривой архитектурой.

И да, ничто не мешает иметь интерфейсы Ромб и Прямоугольник.

Наследовать интерфейсы — хорошо. Наследовать реализацию — не очень. Множественно наследовать реализацию — вообще страх.
И да, ничто не мешает иметь интерфейсы Ромб и Прямоугольник.
Но ведь это не поведение. Вот интерфейс ИмеетПлощать с методом ПосчитатьПлощадь — имеет смысл. Или «РисуемаяФигура» с методом «Нарисовать(холст)» Какой смысл вообще в «Ромбах» и «Прямоугольниках»?
Декларация того, что данный объект ведёт себя как ромб или прямоугольник, хотя вообще ими не является. Правда с трудом представляю, что бы это значило:)
Я лично рассматриваю интерфейс не как поведение, а как часть сущности. Например интерфейс IEnumerator — что-то, что умеет перечислять. Это не поведение, это существительное. Так же и ромб, и прямоугольник — это сущность, которая с некоторой точки зрения (i.e. при касте) является ромбом или прямоугольником.

Поведение это трейты. Похоже не интерфейсы, но немного другое все-таки. У них и названия другие. Интерфейсы — это IEnumrator, IList, ICollection… Трейты — Display/Debug/Clone/Copy/…
что-то, что умеет перечислять. Это не поведение, это существительное
Сначала говорите «умеет перечислять», а потом «существительное».

Конечно, названия у них существительные, но суть — поведенческая. ICollection/IList — содержит элементы и предоставляет определенное поведение. Коллекция вот, к примеру — имеет поведение добавления, удаления и получения элемента. Всё, что коллекция — должно иметь эти поведения. Понимаете о чем я?

Трейты — это вообще что-то из процедурного программирования. «Добавить такую фукциональность». Ну то есть интерфейс говорит «инстанс может содержать и работать с элементами», а трейт говорит: «добавь ка именно такую процедуру».
Не вижу смысла спорить об определениях. Суть не меняется, наследование реализации — признанный антипаттерн в большинстве случаев. Один из редких сценариев где это нужно — GUI, тут я не спорю. Множественное наследование куда злее. Его выкинули из кучи языков во время проектирования не из-за религиозных соображений или потому что кто-то не осилил бросать ошибку компиляции если метод неоднозначен.
Его выкинули из кучи языков во время проектирования не из-за религиозных соображений
А почему?
Там только религиозные соображения.
Multiple implementation inheritance injects a lot of complexity into the implementation. This complexity impacts casting, layout, dispatch, field access, serialization, identity comparisons, verifiability, reflection, generics, and probably lots of other places.

Это религиозные соображения?
Да, ничего конкретного нет.
Какие проблемы с casting?
Генерики вон тоже сложно, в Go их нет, однако они есть а Java, C#,…
Не знаю, я вопрос на таком уровне не изучал. Если бы я покопался в компиляторе пару лет и потом мне дали бы эту тему на ресерч на месяцок, я смог бы дать ответ.

Не вижу повода не верить человеку, который эту экспертизу провел.

Собственно с чего началась эта ветка:

Говорят, множественное наследование сложно, потому что может возникнуть «deadly diamond of death», но эту проблему можно элементарно решить на уровне компилятора.

1. Нет никаких доказательств, что это просто. Зато социальное доказательство обратного — есть
2. У людей нет задачи сделать идеальный компилятор, это невозможно, требуется сделать максимально хороший с ограничением по времени разработки. Если фичу сложно сделать, и она имеет малую ценность — нафиг её, даже если есть валидные сценарии и план резолва неопределенности.
Не вижу повода не верить человеку, который эту экспертизу провел.
Ну хотябы потому что нет ни одного конкретного примера.
Если фичу сложно сделать, и она имеет малую ценность — нафиг её,
Т.е. религиозные соображения
Скорее экономические соображения. Вот «малая ценность» можеь быть объявлена из «религиозных».
Поведение это трейты. Похоже не интерфейсы, но немного другое все-таки. У них и названия другие. Интерфейсы — это IEnumrator, IList, ICollection… Трейты — Display/Debug/Clone/Copy/…

Так трейты — это и есть множественное наследование. Если трейты ок — то и множественное наследование же ок?

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

Реализуют, конечно, с чего бы нет?


По сути трейты чисто технический механизм переиспользования кода.

Ну, как и наследование :)


Один и тот же трейт, например, может реализовывать разные интерфейсы, не связанные друг с другом наследованием.

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


Смотрите, если вам показать множественное наследование и трейты и просить, в каком случае есть что, то вы не сможете определить без подсказки.
Т.к. с точки зрения семантики языка это одна и та же вещь. Нет никакого способа отличить реализацию трейта от наследника класса.
согласитесь, если что-то в точности ведет себя как наследование и неотличимо от него ни в каком контексте, то это наследование и есть.

Реализуют, конечно, с чего бы нет?

Зависит от реализации, конечно. В моём основном языке трейты не реализуют "являются", результат операции тип $obj instanceof TrateName всегда false.


Ну, как и наследование :)

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


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

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

В моём основном языке трейты не реализуют "являются", результат операции тип $obj instanceof TrateName всегда false.

Ну это неконсистентное поведение instaceof в вашем языке, не более того. Если класс реализует трейт, то он является соответствующим подтипом и то, что у вас instaceof не соответствует отношению подтипирования в языке — проблема, конечно.


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

А иерархии абстракций для чего создаются? ;)


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

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


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

Класс не реализует трейт, он собирает себя из трейтов и собственных объявлений.

Ну или приведите своё каноничное определение трейта.
Класс не реализует трейт, он собирает себя из трейтов и собственных объявлений.

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


Ну или приведите своё каноничное определение трейта.

Трейт — это ровно тот же самый абстрактный класс, просто для его наследования зафиксирован более простой и ясный алгоритм выбора конкретного метода, когда у вас при наследовании получается конфликт имен. С-но реализация данного алгоритма выбора при наследовании — и есть "трейты".
Ниже было верно замечено, что обычно трейты не должны иметь состояний, но это несущественно, т.к. легко обходится, пусть и за счет несколько искусственных конструкций (трейт может расширять класс с состоянием).

трейты не должны иметь состояний, но это несущественно
Это существенно, т.к. самый главный аргумент против множественного наследования, которого нет в трейтах. Наследуясь от разных классов, нет возможности не забирать поля одного класса, если они уже есть в другом. С трейтами поля не дублируются никак.
т.к. легко обходится, пусть и за счет несколько искусственных конструкций (трейт может расширять класс с состоянием)
Вы хотите искусственно придумать себе граблей? Как сделать плохо, если нет множ. наследования, но есть трейты? Совсем запретить говнокод язык не может.
Можно узнать источник этого определения? Естественно каноничного, а не из спеки вашего любимого языка, потому что в моём любимом оно, мягко говоря, другое.
Наследуясь от разных классов, нет возможности не забирать поля одного класса, если они уже есть в другом.

Та же самая проблема возникает при наследовании методов.
Кроме того — любое поле можно заменить набором методов.


Вы хотите искусственно придумать себе граблей? Как сделать плохо, если нет множ. наследования, но есть трейты?

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

Та же самая проблема возникает при наследовании методов.
Не возникает.
Кроме того — любое поле можно заменить набором методов.
Поле увеличивает sizeof объекта, увеличивая кол-во его возможных состояний, а метод — не увеличивает sizeof. Может, вы в это высказывание вкладываете какой-то особенный смысл, но я это прочитал как то, что вы нашли способ сжимать информацию без потерь до 0 байт.
Если трейты точно так же позволяют наступать на грабли, то чем они отличаются от обычного множественного наследования?
Так всё развитие языков программирования — это выдумывание всякого синтаксического сахара, чтобы быстрее писать код и меньше наступать на грабли. Трейты отличаются от обычного множественного наследования тем, что их массовое использование не приводит к типичных граблям, которые появляются при массовом использовании множественного наследования.
> Не возникает.

С чего бы вдруг? проблемы то все те же.

> Поле увеличивает sizeof объекта

При чем тут вообще sizeof?

> а метод — не увеличивает sizeof

Зависит от реализации. Может и увеличивать спокойно.

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

Трейты — и есть множественное наследование, так что ваши слова бессмысленны.
Вы хотите сказать, что определенные правила разрешения конфликтов при множественном наследовании приводят к тому, что получается избежать некоторых типичных граблей? Это да.
Я хочу сказать, что множественно наследовать данные плохо, это приводит к свалке в объекте. А множественно наследовать методы — всего лишь синтаксический сахар. Если мы точно можем указать, какой метод вызывать, это всё равно, что методы лежат где-то отдельно и вызываются с аргументом — объектом.
Я хочу сказать, что множественно наследовать данные плохо, это приводит к свалке в объекте.

Так метод — это то же самое поле, в котором просто лежит в качестве данных ф-я. Если множественно наследовать данные плохо — то и методы множественно наследовать тоже плохо.

Так метод — это то же самое поле, в котором просто лежит в качестве данных ф-я
Зависит от реализации. Чаще я вижу методы как static-функции с this-аргументом. И для таких сред ваш аргумент «Если множественно наследовать данные плохо — то и методы множественно наследовать тоже плохо» не работает.
Зависит от реализации. Чаще я вижу методы как static-функции с this-аргументом.

Ну это то же самое, что я сказал.
Еще можно считать методы замыканиями относительно this. Не суть важно, в итоге одно и то же получается.

Вы как-то незаметно перешли от абстрактного ООП в вакууме к конкретной реализации в конкретном ЯП.

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

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


Вопрос «можно ли делать числа объектами» имеет смысл задавать только в контексте теории ООП, где методы и поля — принципиально разные сущности.

Так в теории ООП они одно и то же как раз.

Разницы между полями и методами как раз нет в конкретных реализациях, где методы обычные функции, а функции обычный тип данных, то есть по сути там где ООП построено на базе ФП. В «абстрактном» ООП это не так.
любое поле можно заменить набором методов

Нельзя. Иначе откуда методы будут брать значения полей? Состояние всегда будет где-то храниться.


Простой пример.


class C
{
    int n;

    int getN()
    {
        return this->n;
    }

    int setN(int v)
    {
        this->n = v;
    }
}

Попробуйте убрать поле n и заменить его набором методов.

Попробуйте убрать поле n и заменить его набором методов.

(getN, setN) = let n in (lamda () this->n, lamda (v) this->n = v)

Я не знаю, на каком это языке и что это означает. Но там явно есть this->n, значит никуда вы поле не убрали.

Я не знаю, на каком это языке и что это означает. Но там явно есть this->n

Ну это я скопипастил криво, должно быть просто n, а не this->n :)
Это n из контекста let.

Ага, я ждал такой комментарий. То, что вы переместили поле в глобальное адресное пространство, не означает, что вы его убрали. Часть глобального адресного пространства принадлежит объекту, как и раньше. Это ровно то же самое, что и публичное поле. А контекст в замыкании ведет себя точно так же как объект. JavaScript хороший тому пример.


Кроме того, разговор идет о наследовании, да еще и о множественном, а у вас нет класса C от которого можно было бы унаследоваться. А еще непонятно, как оно должно работать с несколькими объектами, это уже какое-то static поле получается, одно на всех.

Можно узнать источник этого определения?

Источник может быть только один — то общее, что есть в реализации трейтов, среди наиболее распространенных языков.
Так выходит, что, в общем, трейты не отличаются от абстрактных классов ничем, кроме способа разрешения конфликтов.


потому что в моём любимом оно, мягко говоря, другое.

Чем? У вас instaceof сломанный, а с трейтами все ок.

Слово «канонический» не я ввёл в дискуссию. И, да, PHP один из самых распространённых языков и в нём трейты не реализуют отношения «является» или «реализует». Значит согласно вашему же определению, эти отношения не является общим для трейтов, а является деталью реализации в некоторых языках.
Слово «канонический» не я ввёл в дискуссию. И, да, PHP один из самых распространённых языков и в нём трейты не реализуют отношения «является» или «реализует».

Мне непонятно, почему вы говорите, что трейты в php не реализуют отношение "является", если на самом деле реализуют (иначе вы бы не могли вызывать на классе методы трейта, очевидно).
Аналогично и с "реализует" — поскольку в пхп-шных трейтах можно оверрайдить методы, то и "реализует" в том числе есть.

Потому что трейты в PHP несут чисто техническую роль, использование трейта в классе просто технически добавляет классу методы и поля как include, но в области видимости класса. Список трейтов, используемых классом, никак не влияет на то чем являются инстансы класса, от каких классов унаследовано и какие интерфейсы реализует. Никак внешне трейты себя не проявляют, используете вы трейт или скопипастите код из него — никакой разницы. Нет понятия «метод трейта», нельзя инстанс класс, использующего трейт, типизировать как инстанс трейта, как инстанс класса, его базвых классов, в том числе абстрактных, как инстанс интерфейса — можно, а как трейта нелья — это просто деталь реализации, не несущая никакой семантике. Один и тот же трейт может использоваться совершенно разными классами для реализации как собственных методов, так и методов никак не связанных абстрактных классов или интерфейсов.

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


Ну вообще можно выдернуть список используемых трейтов через рефлексию, иногда это даже удобно
Потому что трейты в PHP несут чисто техническую роль, использование трейта в классе просто технически добавляет классу методы и поля как include, но в области видимости класса.

Вы сейчас говорите о конкретной реализации, а я — о том, как оно работает с точки зрения пользователя. Вы пишите трейт, в нем методы, с-но они по факту есть. И это методы трейта. Потому что вот они — написаны. Вы делаете use трейта и теперь можете использовать соответствующие методы — чем это в динамике отличается от наследования?

Вам уже раз 15 сказали что нет отношения is a
Вам уже раз 15 сказали что нет отношения is a

Ну это же неправда. Я могу вызвать методы трейта, с-но отношение is a есть.

методы к is a не имеют никакого отношения. В родителе может вообще методов не быть, а is a будет
Можно проверить правилом подстановки.
Если в контексте, в котором можно использовать A, также допустимо использовать B, то есть отношение B is A.

Для трейтов эта проверка не выполняется.
Если в контексте, в котором можно использовать A, также допустимо использовать B, то есть отношение B is A.

Окей, А — трейт, В — соответствующий класс, приведите, пожалуйста, пример контекста, где можно использовать значение типа А, но нельзя использовать значение типа В.

У трейтов нет значений, т.е. с примерами на значения множество пустое.

Если же трейт A включен в класс B, то класс B, как правило, нельзя включить как трейт в класс C.
У трейтов нет значений, т.е. с примерами на значения множество пустое.

То есть утверждение "в любом контексте, где можно использовать значение типа А, можно использовать значение типа B" — истинно, так?


Если же трейт A включен в класс B, то класс B, как правило, нельзя включить как трейт в класс C.

Потому что он класс а не трейт. Естественно, класс нельзя включать как трейт, почему бы это должно было быть иначе?

То есть утверждение «в любом контексте, где можно использовать значение типа А, можно использовать значение типа B» — истинно, так?
Да. Та формулировка была для is-a у значений. У трейтов нет значений, значит, нужно проверять отношение is-a для типов, т.е. проверять, можно ли использовать имя класса в контексте имени трейта.
Естественно, класс нельзя включать как трейт, почему бы это должно было быть иначе?
Класс не является трейтом, и поэтому is-a не выполняется.
Методы есть, но они ничем для клиента класса не отличаются от ситуации, когда вместо использования трейта мы скопипастим методы из трейта. Ничем не будут отличаться методы трейт от методов класса.

Ну рефлексей можно определить, но рефлексия — это анализ исходного кода, обычные метаданные объекта или класса, его семантика в разрезе ЯП не изменятся в отличии от наследования или имплементации интерфейсов, где меняется семантика, где инстанс наследника или реализации интерфейса одновременно является инстансом всех родителей по цепочке и интерфейсов.

Ничем не будут отличаться методы трейт от методов класса.

А с чего бы они должны отличаться? Это же наследование а не композиция :)

С того, что это наследование, как вы утверждаете. Методы, объявленные в родительском классе или в реализованном интерфейсе отличаются от собственных методов, а в трейтах — нет, если исходники не смотреть.
С того, что это наследование, как вы утверждаете. Методы, объявленные в родительском классе или в реализованном интерфейсе отличаются от собственных методов, а в трейтах — нет, если исходники не смотреть.

Отличаются только в том случае, если они в наследнике переопределены. Если не переопределены — то не отличаются. Аналогично и в трейтах — не переопределили метод => он не отличается.

Отличаются. В этом отличии чуть ли не суть наследования: На инстансе наследника можно вызвать метод родителя как на родителе.
Отличаются.

Отличаются чем? С точки зрения наследника все методы родителя — это его личные методы. Наследник, вообще говоря, даже не обязан знать о том, что он чей-то наследник.


На инстансе наследника можно вызвать метод родителя как на родителе.

Наследованные методы, которые вы вызываете, это методы наследника, а не методы родителя. Именно этим наследование отличается от композиции. При композиции у вас есть "предок" который лежит в отдельном поле, вы к нему обращаетесь и вызываете его методы. В случае наследования этого нет — любой наследник is-a родитель и любые методы, которые были у родителя, становятся его собственными.
Если родитель А обладает методом Х и наследник C is-a A, то С обладает методом Х, просто напрямую. Т.к. мы в утверждении "А обладает методом Х" просто можем заменить А на С.

Нет, не его. И наследник знает, что он чей-то наследник. В коде наследника нет какого-то метода, но он его активно использует через this. Но при условии, что этот метод не private, а если private то будет ошибка нарушения области видимости, а не несуществующего метода.

Ну и ещё есть ранее и позднее связывание, анализ которых подсказывает, что вызывается именно метод родительского класса, при раннем связывании прямо в контексте родителя, а при позднем — в контексте наследника. В коде родительского метода нужно явно указывать, что нужно вызвать не метод определенный в родителе, а что метод самого наследника. Разные есть нюансы. Но в случае наследования нельзя говорить о прямом обладании, это именно наследование.
Но в случае наследования нельзя говорить о прямом обладании, это именно наследование.

В этом случае наследование не задает отношения is-a.


С точки зрения наследника все наследуемые методы — это личные методы наследника. Т.к., еще раз, о том, что он наследник и вообще есть какие-то предки — наследник может и не знать в принципе.

Отношение is-a для внешних потребителей, внутренности инкапсулированы для них, но наследнику доступна информация о своём родителе через конструкции типа parent, с помощью которых наследник может вызвать метод родителя или ещё-что с ним сделать, если сам он переопределил.

Отношение is-a для внешних потребителей, внутренности инкапсулированы для них

Вы сейчас уже отсебятину какую-то выдумывает. Is-a это is-a, то есть любой потомок является своим предком. По-этому он обладает всеми методами предка как своими, в том числе.


но наследнику доступна информация о своём родителе через конструкции типа parent,

Как именно вы обеспечите тот факт, что методы предка являются методами потомка — уже вопрос не относящийся к предмету обсуждения. Можете давать возможность вызова через парент, можете просто скопировать куски кода — это не важно. Важно, что с точки зрения семантики в обоих случаях методы ничем не отличаются от собственных.

Нет, я пользуюсь вашим методом «наименьшего общего». В популярных ООП ЯП методы наследника и родителя не одно и то же. Модификатор private, позднее статические связывание, вызов метода прототипа в JS — всё говорит о том, что только для внешних систем реализуется отношение is-a, а внутри и наследник, и родители могут распознавать контексты. Именно с точки зрения семантики.
всё говорит о том, что только для внешних систем реализуется отношение is-a, а внутри и наследник, и родители могут распознавать контексты

Как?

По разному в разных языках. В PHP, например, по разному обрабатываются self::method(), parent::method() и static::method()
Вот и попробуйте сделать в том же php:
class Foo {
    use TraitBar;
}

$foo = new Foo;
$foo instanceof TraitBar


И внезапно окажется что даже там трейты не подходят под отношении is a

PS: Ну и в php трейты — не совсем трейты
Действительно, такая фича может и отличаться от наследования, но если говорить о трейтах не в контексте конкретной реализации в конкретном языке, то от наследования они не отличаются.
У трейтов существенное ограничение относительно множественного наследования — они не могут включать в себя состояние, т.е. добавлять к классу переменные.

Благодаря этому пропадают вопросы к наследованию, такие как: если объект наследуется сразу от «прямоугольника» и «квадрата», то у него 3 размера — длина, ширина и сторона? Нет, у объекта можно определить один габарит, и длину-ширину трейта «прямоугольника», как и сторону трейта «квадрата» замапить на него.

Особенно хорошо этот принцип работает для сложных иерархий: если Button и EditBox — это GUI-объект, то, если отнаследовать свой контрол от обоих, у каждого всё равно останется свой собственный parent? С трейтами parent будет единственный, определённый в базовом классе.
Множественное наследование без реализации — конечно ок.
Множественное наследование без реализации — конечно ок.

Но трейты — это множественное наследование с реализацией. Без реализации — это интерфейсы.

Но в отличие от тех же интерфейсов трейты не позволяют переопределять их. А при явном указании трейта к которому надо привести не возникает неоднозначности.
Но в отличие от тех же интерфейсов трейты не позволяют переопределять их.

Переопределять что?


А при явном указании трейта к которому надо привести не возникает неоднозначности.

Никто не мешает сделать так же наследования классов.

Переопределять что?

Методы
Никто не мешает сделать так же наследования классов.

Проблема с наследованием состояния еще и в том, что непонятно, должно мы использовать одно и то же поле, или разные.

Допустим, у нас есть трейты Foo и Bar, и нам разрешено в них хранить состояние. У них есть поле count. Мы реализуем эти трейты для какого-то типа. Должны ли мы сделать два поля count?
Методы

А интерфейсы как позволяют методы переопределять?


Проблема с наследованием состояния еще и в том, что непонятно, должно мы использовать одно и то же поле, или разные.

Любой метод является полем. В случае наследования методов мы одно поле должны использовать или разные?


Допустим, у нас есть трейты Foo и Bar, и нам разрешено в них хранить состояние. У них есть поле count.

Допустим, у нас есть два трейта Foo и Bar с методом count(). Мы должны сделать два метода count()?

Любой метод является полем.

Только в ваших фантазиях.

Только в ваших фантазиях.

А чем они, по вашему, отличаются?

Поле, которое можно перезаписать — это состояние и это дополнительная сложность.

Если метод реализован как поле, но не меняется (по соглашениям кодирования), то проблемы он не вызывает при наследовании.
Если метод реализован как поле, но не меняется (по соглашениям кодирования), то проблемы он не вызывает при наследовании.

Ну а если метод внутри обращается к некоторому полю, разному, в зависимости от реализации? Та же проблема в итоге будет.

Метод из трейта не может обращаться к полю напрямую, т.к. в коде трейта поле недоступно. А обращаться через метод-геттер — пожалуйста, с учётом того, что под каждый трейт можно написать свой геттер/сеттер, учитывающий логику трейта.
Метод из трейта не может обращаться к полю напрямую, т.к. в коде трейта поле недоступно.

Он может обращаться к полю parent-класса.

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

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


Смотрите, у вас тот же самый ромб — есть класс А с полем n, есть два трейта Foo и Bar, с методами, использующими это поле, есть класс Yoba extends A with Foo with Bar.
Есть две стратегии разрешения этого ромба — либо раскопировать поле n, либо оставить его одно. В обычном наследовании применяется первая стратегия, в трейтах — вторая.

Разница в том, что при множественном наследовании нет вариантов оставить одно поле (виртуальное наследование не рассматриваем по соображениям производительности).
А интерфейсы как позволяют методы переопределять?

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

Любой метод является полем. В случае наследования методов мы одно поле должны использовать или разные?

Не является, хотя бы с точки зрения потребляемой памяти. Каждое поле клонируется каждым инстансом объекта. Каждый метод является разделяемым и хранится в единственном экземпляре. Сгенерировать лишний метод может быть позволительно, удваивать-утраивать размер объекта — нет.

Допустим, у нас есть два трейта Foo и Bar с методом count(). Мы должны сделать два метода count()?

Да. Сразу говорю, что «ну тогда должны сделать 2 поля count» — не работает, ибо см. выше.
У трейтов же нет понятия «производных классов», потому что нет наследования.

Есть, и методы там переопределяются.


Не является, хотя бы с точки зрения потребляемой памяти.

С точки зрения памяти — может и нет, а с точки зрения семантики — очень даже да.


Да. Сразу говорю, что «ну тогда должны сделать 2 поля count» — не работает, ибо см. выше.

Что см. выше? В случае двух методов те же проблемы будут с памятью. Пусть, например, эти методы — замыкания.

Есть, и методы там переопределяются.

Пример кода?
С точки зрения памяти — может и нет, а с точки зрения семантики — очень даже да.

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

С некоторой точки зрения поле можно представить как пару методов, но нам эта точка зрения в данном случае неинтересна.
Пример кода?
trait yoba {
def foo {...}
}
class bar extends yoba {
override def foo {...}
}


Если факты не укладываются в теорию — тем хуже для фактов?

Какие факты? Если что-то крякает как утка, плавает как утка и выглядит как утка — наверное, это утка. Как раз на основании наблюдаемых фактов.


С некоторой точки зрения поле можно представить как пару методов, но нам эта точка зрения в данном случае неинтересна.

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

trait yoba {
def foo {...}
}
class bar extends yoba {
override def foo {...}
}

Окей, допустим. В этом соглашусь.


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

Конечно имеет смысл, потому что понятия ортогональные. Если в языке вообще НЕТ концепции поля, то иметь какой-либо изменяемый стейт программа не сможет.

Если в языке вообще НЕТ концепции поля, то иметь какой-либо изменяемый стейт программа не сможет.

Может, в замыкании.

Замыкание на переменную со стейтом и есть метод, который записывает данные в поле. В данном случае поле оформленно как замыкание на «типа» локальную переменную.
В данном случае поле оформленно как замыкание на «типа» локальную переменную.

Любое поле оформлено как пара методов, да. Именно это я и пытался доказать :)

Эта пара методов не из воздуха берет значение, и не записывает его вникуда, а писать должно в память. В конечном итоге где-то есть постоянное хранилище (aka поле), в которое идет запись.

Грубо говоря, поле — это уникальная область памяти, используемая этой парой методов. Какой природы эти методы и память — за рамками обсуждения.

Методы хранить дешево, объекты — дорого. Хотя бы по этой причине наследование структуры базового (базовых?) класса(ов?) — плохо.
Грубо говоря, поле — это уникальная область памяти, используемая этой парой методов.

"область памяти" — это низкоуровневое понятие. На том уровне, на котором существует это понятие, нету никаких полей.


Методы хранить дешево, объекты — дорого.

Мы выяснили, что метод — это замыкание.
Вопрос на засыпку — что дешевле хранить: объект или замыкание? ;)

«область памяти» — это низкоуровневое понятие. На том уровне, на котором существует это понятие, нету никаких полей.

Конечно существует. Компилятор как раз такая сущность, которая сопоставляет эти вещи. Раз он может, то и мы можем.


Мы выяснили, что метод — это замыкание.

"мы" это не выяснили. Замыкание может иметь дополнительные данные, метод — нет. Грубо говоря, замыкание это (method, data), а метод это просто method. Так вот второй элемент кортежа — дорогой.

"мы" это не выяснили.

Как это нет?


Замыкание на переменную со стейтом и есть метод
Можно в этом всём разобраться, если вспомнить концепцию делегатов из delphi или c#.

Делегат позволяет вызвать метод объекта без объекта, т.к. сам состоит из двух указателей — на объект и на адрес функции метода.

В дискуссии перепутаны методы из классических ОО ЯП, в которых метод — это атрибут класса (указатель на адрес функции), и не может быть вызван без экземпляра объекта (статические методы не рассматриваем) и «методы» языков типа js, к которых ООП реализовано иначе, а именно замыканиями (которые в c# реализуются делегатами, не являясь методами).

Создавая замыкание (или «метод» в js), фактически создаётся новый объект (безымянный), который может хранить состояние.
В данном случае поле оформленно как замыкание на «типа» локальную переменную.
Любое поле оформлено как пара методов, да.

Нет. Вы заменяете переменную-член и пару методов на другую переменную и пару других методов. Это, очевидно, эквивалентные наборы, и они не эквиваленты наборам «переменная» или «пара методов».
Вы заменяете переменную-член и пару методов на другую переменную и пару других методов.

Все верно, возможность такой замены — это именно то что я с самого начала и хотел показать.

Нет, вы утверждали, что переменная заменяется на пару методов. А это не так, переменную можно заменить только другой переменной.
А это не так, переменную можно заменить только другой переменной.

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

Опять нет) Поле можно заменить переменной. Точка. Если поле было публичное, то никаких 2 методов не надо. Если непубличное, и к нему прилагались геттер и сеттер, то нужна другая пара методов, которая заменит эти геттер и сеттер, а не поле.

В данном контексте странно умалчивать о наличии хранилища в правой части утверждения, так как в левой части речь идет о нем.

Ну ок, теперь понятно, только непонятно, что вы этим хотели доказать. Как это убирает проблемы множественного наследования? Как я понимаю, при такой схеме оно просто невозможно, но для его запрещения есть более простые средства.
На одном из форумов в интернете была замечательная мысль:
«Не понимаю, почему они выбирают между OOP и процедурами? Можно же выбирать между OOP и HTTP»
Сколько не пользуюсь ООП, все равно не уверен, что делаю это правильно.
Идеи ООП описаны для некого абстрактного понятия объекта (класса), но объекты бывают очень разные.
Например, доменные классы. Как их лучше описывать в ООП? Это просто иерархия структур данных или у них могут быть методы? Сколько раз пытался добавить к доменным классам поведение, каждый раз получалось плохо. Сторонние статические хелперы описывались гораздо проще.
Красивее всего с ООП получается писать нечто вроде графических редакторов. Там все как по книжке: базовые классы задают общую концепцию системы, затем они расширяются разными способами и реализуют все необходимое многобразие функций.
Самой загадочной и сложной для меня областью применения ООП являются всякого рода обрабатывающие информацию сервисы, которые должны принимать запросы, следить за изменениями внешних данных, анализировать, вычислять, отправлять ответы, формировать некие события, воздействия и т.д. Программа все время пытается превратиться в ком из каких-то менеджеров неопределенного назначения, тесно взаимодействующих с нарушением инкапсуляции и внутри построенных по сути на обычной процедурной концепции. Если в таких задачах попытаться создать более мелкие объекты и решать задачи за счет их взаимодействия, то для меня вообще все превращается в сущий ад.
Было бы очень интересно где-то прочитать про best practices, особенно по последней задаче.
Не нужно гнаться за «объективизацией», как за самоцелью. Применяйте, если это делает ваш код короче (и проще в поддержке). Большое приложение может состоять из независимых кусков — сервисов, каждый из которых предоставляет OOP-like интерфейс для внешнего мира, но не обязан быть объектно-ориентированным внутри. Также нет никаких требований объединять все классы приложения в единую мега-иерархию.
Не нужно гнаться за «объективизацией», как за самоцелью. Применяйте, если это делает ваш код короче (и проще в поддержке).
Это разумный подход. Но всегда найдется программист Java, который скажет что ООП отлично подходит для задачи, просто «вы не умеете его готовить».

Приходит Java-прогер в столовую и говорит: приготовьте мне oop OOP = new OOP();

OOP oop = new OOP();

Не бывать мне java-программистом:D

Красивее всего с ООП получается писать нечто вроде графических редакторов.
это пока вы не реализуете Undo/Redo и банальное сохранение.
Думаю, если поведение undo/redo/save и поведение при редактировании инкапсулировать в объекты, то действительно все станет плохо. Во всяком случае, мне такие вещи не удаются.
Я думаю проблема в том, что undo/redo/save — это более низкий уровень абстракции по сравнению с основными функциями объекта в графическом редакторе. А поведение при редактировании — это общее поведение группы объектов а не отдельного объекта.
Лично я для undo/redo/save делал объекты не plain и все изменения проводил через отдельный менеджер. Поведение при редактировании реализовывал отдельными классами-обработчиками.
ООП в классическом виде хорошо работает для задач типа отображения на различных устройствах с общим базовым классом различных графических объектов, так же имеющих общего предка и прочие функции того же уровня абстракции.
А в чем с ними проблема?
Undo/Redo типично реализуются через паттерн Команда, который существует как раз благодаря ООП. В чем проблема то?
Ну, большинство ООП-паттернов создано чтобы обойти ограничения ООП :) Так что «паттерн существует благодаря ООП» звучит как «полиция существует благодаря преступникам». Причинно следственная связь отражена вроде верно, но есть нюансы.
Вообще насколько я понимаю паттерны появились естественным путем, через выделение часто используемых конструкций для решения задач определенного вида.
Именно. В языке конструкции не предусмотрено для решения этой задачи, а хорошее решение из нескольких конструкций не очевидно.
Этот паттерн предполагает, что поведение графических объектов при редактировании вынесено во внешние классы, а сами графические объекты работают просто как структуры данных, изменяемые внешним алгоритмом на основе команд.
При наивном восприятии идеи ООП объекты должны сами знать как им реагировать на те или иные ситуации при редактировании. В этом случае команды могут быть не обратимыми.
Простой пример: мы двигаем в редакторе элемент, который связан с другим элементом и они должны двигаться вместе. Команда знает о первом объекте, но не знает что попутно сдвинула и второй. Тогда undo команды может работать не правильно. Значит правила поведения при редактировании нельзя инкапсулировать в объекты.
Не предполагает он вынесения поведения во внешние классы. Более того, он служит ещё большей инкупсуляции поведения, когда инвокер даже не знает какой ресивер будет обрабатывать команду, не гоаоря о том, как ей обрабатывать.

Обработку движения элемента, связанного с другими решается и при наивном восприятии ООП, моделируя связи как свойство объектов, например.
Не приходилось пользоваться этим паттерном. Если вся логика поведения при редактировании реализована в объектах, тогда как заставить эту логику не работать при отмене команды? Вероятно, команда должна передавать графическому объекту в методы особый признак типа «измениться и выполнить поведение редактирования / только измениться»?
Обще-философски для меня проще писать код в стиле «если графические объекты A,B,C попали в определенную ситуацию, то с ними нужно сделать следующее...» чем код в стиле «если я объект A и я попал в определенную ситуацию, то я должен сделать следующее...».
Второй вариант — это как заставить клеточный автомат сделать нечто нужное подбором правил. Очень сложно получить нужное глобальное поведение инкапсулированными в объектах а