Комментарии 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 aa{«123123»};
Писать метод в функциональном стиле
std::string reverse(const std::string& src);
или в процедурном
void reverse(std::string& str);
Но в ООП это никак не заходит.
Угу опять вопрос мутабельности. Это холивар. А утилитарно всё зависит от цели:
- std::reverse() — swap
- std::string::rbegin() — реверсный итератор
- 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) не укладывается в классическую парадигму
> Не совсем понимаю где проблема
Что есть неудобства в ООП, когда объекты предоставлены из внешней (стандартной) библиотеки, и тут приходится съезжать на ПП или ФП.
Сам-то я не против ООП, но не отрицаю его проблемы.
В сравнении с полноценным наследованием, тут недоступны protected-поля и методы, оставленные автором объекта для расширения его функциональности. Нельзя добавить новое поле, и т.п. ограничения.
Любую мономорфную функцию в языке без подтипирования.
Все-таки при формулировке теоремы не стоит забывать про условия.
семантически чётко и ясно. Или всё-таки где-то проблемы?Это потому что конкатенация предусмотрена создателями библиотеки std. Но описание перегрузки оператора умножения над двумя std::vector«double», чтобы считалось скалярное произведение, выходит за рамки ООП.
Вы наверное имели в виду STL. И что вам мешает следовать хорошему тону STL, и писать такие-же библиотеки. В STL кстати есть functional в котором в том числе ести и функциональный класс std::multiplies.
Самое главное, вас ведь никто не ограничивает писать то как вы хотите и как считаете нужным, главное что-бы вам удобно было. А зависать над вопросом "в какой парадигме мне это реализовать", — не продуктивно. Выбирайте хоть ФП, хоть ООП, хоть haskel хоть Java. И реализуйте это то как считаете верным. Какой смысл в войне между отвёрткой и гаечным ключём? Никакой! И тот и другой инструмент может понадобится.
Вопрос не глупый. Например 1.Add(2)
явно должен создавать новый объект 3
, а не менять первый константный аргумент.
Это потому, что у вас слева rvalue. А вот так совсем не явно:
auto foo = 1
foo.Add(2) // foo += 2
Присвоение, разумеется. Изменится лишь локальная переменная.
Мембер принадлежит обьекту, значит и работает он с обьектом, какой то вопрос глупый, с чего бы вообще операции создавать новый обьект
Вопрос не глупый. Например 1.Add(2) явно должен создавать новый объект 3, а не менять первый константный аргумент.
Эмм, где подмена?
Реализуете интерфейс Add и все. Сигнатура этого метода явно должна сказать, что происходит. Например вот.
Инкапсуляция прекрасна, но, как любой инструмент, ей есть границы применимости. И то, что в современные ООП языки интенсивно проникают ФП элементы — свидетельство ограниченности классического ООП.
Инкапсуляция — это вообще свойство любой программы в любой парадигме. Вот написали вы let loadFromDatabase = loadFrom database
— инкапсулировали загрузку из бд! Только вот ООП тут ни на гран.
Наследование — тоже хорошо там, где оно уместно. Проблема только в том, что оно резко сжимает пространство маневра при разработке сложных систем, композиция гибче. В реальной жизни мы используем одни и те же предметы как принадлежащие разным иерархиям наследования, просто не задумываемся об этом. В ООП же объект может принадлежать только одной иерархии (про cpp в курсе).
Композиция и наследование ортогональны. Спросити у растовчан, насколько им приятно писать UI без наследования, услышите массу интересного.
Если, например, говорят про процедурное программирование, то говорят про него абсолютно спокойно
Чистое процедурное программирование — это треш, угар и содомия с логарифмическим графом зависимостей скорости разработки от количества кода. Говоря проще: Больше кода и больше проблем, где-то в некой точке X переходящей в уничтожение как личности каждого, кто попытается добавить туда ещё какую-нибудь процедурку/функционал.
Это самый очевидный подход для любого новичка и совершенно мёртвый в современном мире. Хотя есть и исключения, вроде такой известной штуки как Wordpress, которую пока не материл только ленивый.
По-моему о процедурном подходе как раз и не говорят ничего плохого, т.к. это удел либо мелких одноразовых тулзовин, либо работы новчиков, которые (новички т.е.) потом перерастут этот стиль и начнут писать более адекватный код без засирания состоянием всей программы.
P.S. А на ООП сваливаются все камни по той причине, что это некое «над» решение, которое успешно совмещает (в реальной жизни имеется ввиду, а не сферическом ваккууме) под своим крылом и функциональщину, и процедурщину, и декларативное, аспектное, контрактное, прототипное и прочие программирования. Это стержень архитектуры, а остальное — это лишь решение частных мелких проблем/алогритмов (ФП — это вообще ярковыраженный представитель таких решений, где для решения частной задачи надо понаклепать с десяток функций, которые потом композицией выстраиваются в красивую программку).
Ещё пример: микроконтроллеры. Довольно мало любителей писать под них на плюсах. Ну, потому что ограничение ресурсов слишком жёсткое. Это уже не говоря про FPGA, где свой маленький мир.
И не забудьте про мои любимые шелл-скрипты, которые чуть менее, чем полностью состоят из процедурного подхода.
Я не говорю, что C++ и ООП плохо, просто есть места, где их использовать не стоит.
Чистое процедурное программирование — это треш, угар и содомия
Это самый очевидный подход для любого новичка и совершенно мёртвый в современном мире.
По-моему о процедурном подходе как раз и не говорят ничего плохого, т.к. это удел либо мелких одноразовых тулзовин
Расскажите это: Линусу Торвальдсу или создателям почти любой ОС, создателям apache, nginx, tarantul, и т.д.
Выбор инструмента, это исключительная прерогатива создателя ПО.
Расскажите это: Линусу Торвальдсу или создателям почти любой ОС, создателям apache, nginx, tarantul, и т.д.Вот только они не написаны в чистом процедурном стиле. То что в C нет классов не значит что в нем нельзя использовать ООП.
Одна из существенных проблем ООП, кмк, — реализация операций. То есть на классическую парадигму оно очень плохо ложится. Сложение чисел или конкатенация — тому яркий пример: objA.plus(objB) или objB.plus(objA)
Если перекладывать эту операцию на реальный мир, то всё зависит от того, что мы делаем. При наличии у нас двух коробок с апельсинами, мы можем переложить их из одной в другую, тем самым изменив состояния одной из коробок. При этом мы можем все апельсины переложить в третью коробку, тем самым создав новый объект (заодно выкидываем старые коробки). То есть всё зависит от конкретных требований.
Также не совсем очевидны выгоды от иммутабельности. Возможно в каких-то случаях она полезна, сложно сказать. Как по мне — то проще найти кто меняет данные и разобраться с проблемами, чем запрещать мутабельность вообще. Может, конечно, языки такие, что это сделать сложно или невозможно. В Delphi это делается элементарно.
В частности, иммутабельный объект всегда потокобезопасен и реентерабелен без дополнительных усилий. В отличии от мутабельных объектов.
А вот в тех языках, где всё иммутабельное выбора, увы, нет.
Нужен ли такой выбор программисту, если компилятор потом сам приведет к оптимальному виду?
1. Со строками компилятор С# действует подобным способом
2. В функциональных языках на уровне «для программиста» (как это делается в рантайме отдельный вопрос) иммутабельность реализуется путем создания копии, т.е. программисту в принципе нет необходимости реализовывать иммутабельный словарь
Также не совсем очевидны выгоды от иммутабельности. Возможно в каких-то случаях она полезна, сложно сказать.
Иммутабельность полезна в случаях, когда создаётся кусок данных (объект), который используется в нескольких местах и/или в разных потоках. Тогда вероятность того, что такой объект ВНЕЗАПНО поменяется, равна нулю. К слову, иммутабельность легко реализуется на любых ООП-языках (может, и не только ООП) — достаточно не писать сеттеров и не менять поля объекта кроме как в конструкторе. А вот наоборот — "чуть" сложнее.
достаточно не писать сеттеров и не менять поля объекта кроме как в конструкторестандартный дополнительный вопрос: «а если я в конструктор передам изменяемый nestedObject? И потом изменю его состояние?». В результате беседы приходим к выводу, что вся иерархия объектов должна быть неизменяемой. И что вручную это сделать накладно, если в языке какой-нибудь Integer или String по своей природе mutable.
P.S. В основном я с вами согласен — неизменяемость полезна при многопоточности и может здорово упростить жизнь. Я лишь хочу подчеркнуть, что гарантии неизменяемости «из коробки» полезны. Особенно если есть четкая грань, что mutable а что нет. Например scala.mutable.Array vs scala.immutable.List — сразу понятно что к чему даже новичку в языке.
То ничего не изменится — наш объект останется иммутабельным (если забыть про рефлексию и подобные трюки). :)
а если я в конструктор передам изменяемый nestedObject? И потом изменю его состояние?
Вы просто не сможете это сделать:
Error: cannot implicitly convert expression bar of type Bar to immutable(Bar)
иммутабельность легко реализуется на любых ООП-языках (может, и не только ООП) — достаточно не писать сеттеров и не менять поля объекта кроме как в конструктореВ вашем примере неизменяемость реализована за счет ключевого слова immutable — такое есть не во всех языках.
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);
}
class InputDevice : public Device { ... }
или class InputDevice {
public: Device device;
}
Вы имеете ввиду аллокацию на стеке? А нет ли в с++ оверхеда по памяти на сам объект класса? То есть если внутри класса Device будет еще одно поле
class Device {
public: AnotherDevice device;
}
А внутри AnotherDevice еще одно поле с инстансом другого класса и т.д, и если будет такая цепочка из тысячи вложенных полей вырастет ли итоговый размер объекта InputDevice в сравнении вариантом когда строим цепочки через наследование или нет?
public: AnotherDevice device;
это не указатель, это сам объект AnotherDevice, все его поля. Компилятор знает, с какого смещения начинается вложенный объект и формирует указатели на вложенный объект, когда они нужны, добавлением константы к указателю на объект-композит.
Для каждого включенного объекта с вирт. методами будет на 1 указатель больше (vptr).
Однако, если не наследоваться, виртуальные методы не нужны :D
Как то раз делал тестовое задание при приеме на работу. Нужно было написать демона, который пингует адрес и шлет репорты, если пинг не проходит. Там кода 10 строчек. Ну я и написал просто 10 строчек. Понятно, что для демонстрации скиллов этого не достаточно и я прикрутил демону ООПешную возможность прикручивать разные способы отправки репортов. Но все равно первые же два вопроса были «Почему не весь код ООП?», «Почему не использовали фреймворков?». Прикручивать какойнибудь симфони ради 10 строчек кода, да еще и демону?.. На работу меня тогда не взяли…
И хорошо что не взяли, Вам же лучше, вы просто не застряли в болоте. Когда парадигма, — самоцель и когда годами размышляют над иерархией классов и тремя шаблонами, или над мутабельностью/иммутабельностью, — ничего хорошего из этого не выходит.
Вообще это такой суровый ынтырпрайз, не написал 2000 строк и всё через ООП вместо десяти которыми можно решить задачу. При этом перед тем как писать код ещё и UML диаграммы все нарисовать от use-case и заканчивая deployment. Это как дрескод нарушуть и в плавках на банкет явиться.
- ООП смешивает данные и действия над ними. Это плохо
Думаю, Вы не совсем правильно поняли этот аргумент. Плохо когда пытаются абсолютно всё представлять объектами, искусственно симулируя наборы данных и действий там, где их нет: число 3 это не объект, равно как и функция sum(a,b int) не является частью/методом какого-то объекта. Всё хорошо в меру, где-то используемые кодом абстракции действительно лучше представлять в виде объектов, но требование чтобы объектом было абсолютно всё добавляет accidental complexity на пустом месте.
- Наследование закрепощает программу, делает трудным внесение изменений
Настоящее отношение is a между двумя разными абстракциями в реальном коде встречается крайне редко. А когда его пытаются насильно внедрить ради формального следования DRY случается беда. Поэтому наследование оказалось далеко не таким полезным инструментом, как его нам пытались п(р)одать евангелисты ООП. Из-за этого было написано очень много кода, которое использовало наследование слишком интенсивно и почти всегда не к месту, что и вызвало проблемы. Да, инструмент не виноват, но конкретно этот оказался настолько слабо применимым и проблемным, что ему место на дальней полке, и, по хорошему, разрешать им пользоваться можно только очень квалифицированным разработчикам.
- Методология ООП изначально ошибочна
Абсолютно необоснованный аргумент. ООП создавалось для того, чтобы моделировать своеобразный виртуальный мир, состоящий из объектов, как и наш мир.
Наш мир не состоит из объектов. Это просто один из способов смотреть на мир, и способ довольно корявый и ограниченный. Иными словами эта абстракция не очень корректна, поэтому попытки применять её часто дают не очень хорошие результаты. А поскольку эта идея подаётся как основание ООП… Ничего не имею против ООП, но вот этот бред про "мир состоит из объектов" надо забыть как можно быстрее, он больше вредит ООП чем помогает.
- Но даже миллионы мух не убедят нас, что навоз — это вкусно
Мол, массы в большинстве своём глупы (всё же я не думаю, что это относится и к программистам), бегают по «модным шмоткам» и восхищаются ими.
Программисты, в первую очередь, тоже люди. И многие действительно ведутся на модные технологии. Не потому, что они глупы, а потому, что просто быть умным недостаточно. Здесь на хабре иногда попадаются вполне умные разработчики, которые часто смотрят телевизор, и верят в то, что там рассказывают. Одного ума мало, чтобы противостоять давлению рекламы, моды и политтехнологий. К сожалению.
Плохо когда пытаются абсолютно всё представлять объектами, искусственно симулируя наборы данных и действий там, где их нет
Это называется ООП головного мозга. Все хорошо в меру. Есть даже такой софистический прием — довести идею до обсурда, а потом на основе этого доказывать неверность идеи.
А когда его пытаются насильно внедрить ради формального следования DRY случается беда.
Тут даже из вашего примера следует, что проблема не в ООП, а в том что у когото DRY головного мозга. До меня однажды докапались, почему у меня дважды написаны 4 одинаковые строчки, мол я должен вынести все в функцию. Только вот каждая из этих строчек уже была функцией, но с разными параметрами. Вынесение в функцию вылилось бы в усложнение кода и даже в увеличение количества строк. Но человеку было все равно, донт репит ёрселф и все тут.
Тут надо отойти в сторонку и сказать, зачем нужны ООП/ФП/etc. Основная цель любой методологии и любого нормального языка программирования — упростить решение задач. Определенных задач, разумеется, потому что универсального подхода пока никто не придумал. В программировании простота решения коррелирует с количеством кода, которое ты должен написать в прикладной программе. Пишешь меньше кода — тратишь меньше времени, упрощаешь поддержку продукта. Именно с этой точки зрения надо смотреть на языки программирования. Категории «труъ» и «не-труъ» интересны только как индикаторы достижения цели.
Так вот, для упрощения работы программиста в незапамятные времена были придуманы библиотеки процедур и вообще структурное программирование. Сейчас апологеты композиции (composition over inheritance) говорят, что вам достаточно скомбинировать библиотечные функции нужным образом чтобы получить требуемое сложное поведение. Это так, но таковая композиция сама по себе является сложной сущностью. Аналог такой сущности — стойка с сетевым оборудованием. Все компоненты в стойке стандартны, но конкретная конфигурация стойки — нет.
Что делать, если нам нужно переиспользовать такую композицию? В функциональном подходе можно сделать функцию, которая будет собирать составляющие ее функции нужным образом. Это отлично работает до тех пор, пока нам не потребуеся подменить одну фукцию из частей композиции на другую. Как это сделать без копирования всей композиции и замены одного имени на другой? Передавать этот подменяемый кусочек в параметрах? Неудобно, ибо в композиции может быть большое количество заменяемых частей, и нам придется предавать целую кучу параметров.
Класс в ООП — это по сути композиция функций, в которой можно подменять отдельные компоненты. Для этого там существует наследование и полиморфизм. Все просто.
По-сути, без наследования (и связанного с ним полиморфизма) ООП особого смысла не имеет
Полиморфизм — безусловно правильная штука. Только вот к наследованию его привязали именно в ООП, а вообще полиморфизм отлично существует и без наследования. В качестве примера посмотрите на интерфейсы в 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, невозможно понять его смысл, пока нет объекта, который этот смысл упаковывает, и методов, которые этот смысл поясняют.
Расскажите это на уроке математики.
Хоть вы и не ответили на мои вопросы из прошлого сообщения, я все рано задам еще. Вы действительно думаете, что числа — это что-то не имеющее смысла? А если смысл всё-таки есть, то разве методы (операции, возможности) вроде "+", "-", "*", "/" существуют не для пояснения смысла, заключенного в число?
А если смысл всё-таки есть, то разве методы (операции, возможности) вроде "+", "-", "*", "/" существуют не для пояснения смысла, заключенного в число?
Только вы все напутали. +-*/ — это как раз объекты, а вот у них уже в свою очередь есть метод apply, который принимает в качестве аргументов числа: plus.apply(1,2)
Объекты имеют состояние
Могут иметь. А могут — не иметь.
Или this = 1; plus(this, 2), для чего есть запись 1.plus(2) :)
Тогда у каждого числа свой plus, что семантически неверно, операция-то только одна.
Тогда у каждого числа свой plus
С чего бы? Методы как бы общие для всего класса чисел.
Но дело-то не в них. Существует множество возможностей представить предметную область в виде набора объектов, и ни один из них не является заведомо правильным и семантичным.
Простейший такой способ — каждое число как объект. Другой способ — операция как объект.
У каждого способа своя сфера применения. Первый случай больше подходит для создания собственных алгебр, второй — для написания парсеров, интерпретаторов, компиляторов или проведения символьных вычислений.
С чего бы? Методы как бы общие для всего класса чисел.
Нет, в ООП вообще у каждого объекта свой метод. Просто часто они совпадают.
Первый случай больше подходит для создания собственных алгебр
Первый вообще ни в каких случаях не подходит.
В ООП вообще нет методов, с одной стороны
Нет, методы то везде есть, они только могут называться по-другому (сообщения, например).
чаще всего все методы одного класса
Методы не у класса, а у объекта. ООП ортогонально классам (на то оно и объектно-ориентированное, а не классо-ориентированное :)).
ООП с классами — это лишь один из вариантов.
Скорее методы очень частный случай сообщений
Это не частный случай, а просто разные названия одного и того же :)
Ну и плюс собственно посылку сообщения можно реализовать через вызов метода.
Везде есть функции. А вот диспетчеризации их вызовов могут быть разные. Это может быть прямой вызов конкретной функции (в том числе невиртуальные методы). Может быть выбор наиболее подходящей функции в зависимости от типа объекта (виртуальный метод), может быть выбор на основе всей сигнатуры (множественная диспетчеризация). Посылка сообщения — это вызов функции приёма сообщения с параметрами: объкт и сообщение. А как она диспетчеризируется зависит от языка.
Но чем это плохо? Почему одной операции не может соответствовать целый класс методов?
Почему одной операции не может соответствовать целый класс методов?
А что это значит с точки зрения семантики? Мы же моделируем предметную область, так?
Они могут быть одинаковы, например описываться одной формулой, но они принадлежат объекту.
Ну так операции над числами не принадлежат числам. Они не являются ни событиями, ни реакцией на эти события.
Это лишь условность. Захотим чтобы принадлежали — будут принадлежать.
Так я же и спрашиваю — с точки зрения семантики предметной области это какой смысл имеет?
Так-то мы можем куда угодно засунуть какой угодно метод, например почему бы арифметическим операциям не быть методами уток? Или плюс — а утки, а умножение — у жирафа?
Надо что-то умножить? Отлично! Создаете жирафа с нужной шеей при помощи фабрики жирафов и умножаете! :)
Бессмысленно? Не более, чем иметь операцию плюс у числа.
Простите, а чем вас "сложи себя вот с этим числом и сообщи результат" в качестве семантики не устраивает-то?
Сложение можно представить как сообщение числу «я хочу увеличить тебя на вот это число», а результат как «да, я себя увеличил, теперь у меня новое состояние».
"Состояние у числа" — это уже совсем что-то шизофреническое, уж извините.
Только в рамках классической математики может выглядеть странно.
Замечательно, я не против того, чтобы кто-то сделал свою неклассическую математику, но только тогда ваши числа — не числа.
А то, что для не-чисел может быть все не так как для чисел — это, конечно, верно.
Модель программы вовсе не должна следовать общепринятым математическим и прочим моделям.
Да, модель не обязана следовать чему бы то ни было, но обычно такая модель — говно, а не модель.
Попробуйте отказаться от такого:
i=i+1
Попробуйте отказаться от такого:
- зачем?
- что тут чему противоречит?
зачем?
Потому что такая запись противоречит общепринятым математическим моделям, где изменение значения принято обозначать другой переменной (Ti, Ti+1, Ti+2 и т.д., вместо T, T, T).
что тут чему противоречит?из i=i+1 следует противоречие 0=1
Потому что такая запись противоречит общепринятым математическим моделям
Почему противоречит? Нет.
из i=i+1 следует противоречие 0=1
Нет, не следует, потому что = — это присваивание, а не сравнение.
Но обозначается тем же символом.
Какая разница, каким символом оно обозначается? Важно не как вы что обозначаете, а какой эти символы несут смысл.
Шизофрения — это когда у вас что-то, что по смыслу должно быть числом, не ведет себя как число.
Обозначать же вы можете что угодно как угодно, и то, что в разных языках одни и те же вещи могут обозначаться по-разному (а формальный язык ZFC и язык какого-нибудь ЯП — это разные языки) не является чем-то странным.
Значение переменной не меняется. Иначе это не переменная, а функция, либо последовательность.
Вместо присваиваний в математике есть введение обозначений (аналог 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
Пример, спроси у бухгалтера что такое налоговая ставка, он скажет что это число, которое...
Не важно, что он скажет, важно, что он будет иметь ввиду. Мы ведь люди и общаемся на ЕЯ. Если вы будете такие слова бухгалтера буквально понимать, у вас, очевидно, проблемы.
В данном конкретном случае — я не думаю, что вы будете для decimal делать метод "увеличить_ставку".
Но ведь это будет метод именно TaxRate, а не метод "числа" :)
Видите, вы верно поняли бухгалтера (не смотря на то, что "формально" он вроде как дичь сказал) и все в порядке.
А почему для этой задачи не использовать персептрон? Он лучше, на ваш взгляд, справится?
Угу, но если я задам вопрос: "вы добавили метод к 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()
Автор поднял правильную тему, и в правильном ключе. Спасибо.
По моим впечатлениям, самые полярные и непримиримые мнения по функциональщине.
Ну не знаю, по-моему вполне однозначные мнения — высокий порог входа и хорошая пригодность для определенных задач.
Другая проблема, что сейчас много людей говнокодят на JS в процедурном стиле с элементами ФП и топят, что они — функциональщики, потому что отказались от классов.
А какую проблему по вашему должно решать множественное наследование? От обычного-то обычно предлагается отказаться в пользу композиции, про множественное я и молчу. Если у вас объект и швец, и жнец, и игрец, то это уже полное нарушение SRP.
И да, ничто не мешает иметь интерфейсы Ромб и Прямоугольник.
Наследовать интерфейсы — хорошо. Наследовать реализацию — не очень. Множественно наследовать реализацию — вообще страх.
И да, ничто не мешает иметь интерфейсы Ромб и Прямоугольник.Но ведь это не поведение. Вот интерфейс ИмеетПлощать с методом ПосчитатьПлощадь — имеет смысл. Или «РисуемаяФигура» с методом «Нарисовать(холст)» Какой смысл вообще в «Ромбах» и «Прямоугольниках»?
Поведение это трейты. Похоже не интерфейсы, но немного другое все-таки. У них и названия другие. Интерфейсы — это IEnumrator, IList, ICollection… Трейты — Display/Debug/Clone/Copy/…
что-то, что умеет перечислять. Это не поведение, это существительноеСначала говорите «умеет перечислять», а потом «существительное».
Конечно, названия у них существительные, но суть — поведенческая. ICollection/IList — содержит элементы и предоставляет определенное поведение. Коллекция вот, к примеру — имеет поведение добавления, удаления и получения элемента. Всё, что коллекция — должно иметь эти поведения. Понимаете о чем я?
Трейты — это вообще что-то из процедурного программирования. «Добавить такую фукциональность». Ну то есть интерфейс говорит «инстанс может содержать и работать с элементами», а трейт говорит: «добавь ка именно такую процедуру».
Его выкинули из кучи языков во время проектирования не из-за религиозных соображенийА почему?
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?
Не вижу повода не верить человеку, который эту экспертизу провел.
Собственно с чего началась эта ветка:
Говорят, множественное наследование сложно, потому что может возникнуть «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-аргументом. И для таких сред ваш аргумент «Если множественно наследовать данные плохо — то и методы множественно наследовать тоже плохо» не работает.
Вопрос «можно ли делать числа объектами» имеет смысл задавать только в контексте теории ООП, где методы и поля — принципиально разные сущности. Просто потому что во всех конкретных практических ЯП вопрос «как правильно делать числа» давным давно решен.
Вы как-то незаметно перешли от абстрактного ООП в вакууме к конкретной реализации в конкретном ЯП.
Я как раз нигде не переходил, потому и не различаю поля/методы и т.д.
В "абстракном ООП" разницы между полем и методом нет. В каком-то конкретном языке с конкретной реализацией ООП — она может присутствовать, да.
Вопрос «можно ли делать числа объектами» имеет смысл задавать только в контексте теории ООП, где методы и поля — принципиально разные сущности.
Так в теории ООП они одно и то же как раз.
любое поле можно заменить набором методов
Нельзя. Иначе откуда методы будут брать значения полей? Состояние всегда будет где-то храниться.
Простой пример.
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 несут чисто техническую роль, использование трейта в классе просто технически добавляет классу методы и поля как include, но в области видимости класса.
Вы сейчас говорите о конкретной реализации, а я — о том, как оно работает с точки зрения пользователя. Вы пишите трейт, в нем методы, с-но они по факту есть. И это методы трейта. Потому что вот они — написаны. Вы делаете use трейта и теперь можете использовать соответствующие методы — чем это в динамике отличается от наследования?
Вам уже раз 15 сказали что нет отношения 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, то С обладает методом Х, просто напрямую. Т.к. мы в утверждении "А обладает методом Х" просто можем заменить А на С.
Ну и ещё есть ранее и позднее связывание, анализ которых подсказывает, что вызывается именно метод родительского класса, при раннем связывании прямо в контексте родителя, а при позднем — в контексте наследника. В коде родительского метода нужно явно указывать, что нужно вызвать не метод определенный в родителе, а что метод самого наследника. Разные есть нюансы. Но в случае наследования нельзя говорить о прямом обладании, это именно наследование.
Но в случае наследования нельзя говорить о прямом обладании, это именно наследование.
В этом случае наследование не задает отношения is-a.
С точки зрения наследника все наследуемые методы — это личные методы наследника. Т.к., еще раз, о том, что он наследник и вообще есть какие-то предки — наследник может и не знать в принципе.
Отношение is-a для внешних потребителей, внутренности инкапсулированы для них
Вы сейчас уже отсебятину какую-то выдумывает. Is-a это is-a, то есть любой потомок является своим предком. По-этому он обладает всеми методами предка как своими, в том числе.
но наследнику доступна информация о своём родителе через конструкции типа parent,
Как именно вы обеспечите тот факт, что методы предка являются методами потомка — уже вопрос не относящийся к предмету обсуждения. Можете давать возможность вызова через парент, можете просто скопировать куски кода — это не важно. Важно, что с точки зрения семантики в обоих случаях методы ничем не отличаются от собственных.
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 {...}
}
Окей, допустим. В этом соглашусь.
Правильно. По-этому нет никакого смысла вообще разделять поля и методы (на самом-то деле разница в семантике обычно есть, связанная с рантаймом языка, но для данного конкретного обсуждения она неинтересна и несущественна).
Конечно имеет смысл, потому что понятия ортогональные. Если в языке вообще НЕТ концепции поля, то иметь какой-либо изменяемый стейт программа не сможет.
Если в языке вообще НЕТ концепции поля, то иметь какой-либо изменяемый стейт программа не сможет.
Может, в замыкании.
В данном случае поле оформленно как замыкание на «типа» локальную переменную.
Любое поле оформлено как пара методов, да. Именно это я и пытался доказать :)
Грубо говоря, поле — это уникальная область памяти, используемая этой парой методов. Какой природы эти методы и память — за рамками обсуждения.
Методы хранить дешево, объекты — дорого. Хотя бы по этой причине наследование структуры базового (базовых?) класса(ов?) — плохо.
Грубо говоря, поле — это уникальная область памяти, используемая этой парой методов.
"область памяти" — это низкоуровневое понятие. На том уровне, на котором существует это понятие, нету никаких полей.
Методы хранить дешево, объекты — дорого.
Мы выяснили, что метод — это замыкание.
Вопрос на засыпку — что дешевле хранить: объект или замыкание? ;)
«область памяти» — это низкоуровневое понятие. На том уровне, на котором существует это понятие, нету никаких полей.
Конечно существует. Компилятор как раз такая сущность, которая сопоставляет эти вещи. Раз он может, то и мы можем.
Мы выяснили, что метод — это замыкание.
"мы" это не выяснили. Замыкание может иметь дополнительные данные, метод — нет. Грубо говоря, замыкание это (method, data)
, а метод это просто method
. Так вот второй элемент кортежа — дорогой.
"мы" это не выяснили.
Как это нет?
Замыкание на переменную со стейтом и есть метод
Делегат позволяет вызвать метод объекта без объекта, т.к. сам состоит из двух указателей — на объект и на адрес функции метода.
В дискуссии перепутаны методы из классических ОО ЯП, в которых метод — это атрибут класса (указатель на адрес функции), и не может быть вызван без экземпляра объекта (статические методы не рассматриваем) и «методы» языков типа js, к которых ООП реализовано иначе, а именно замыканиями (которые в c# реализуются делегатами, не являясь методами).
Создавая замыкание (или «метод» в js), фактически создаётся новый объект (безымянный), который может хранить состояние.
В данном случае поле оформленно как замыкание на «типа» локальную переменную.Любое поле оформлено как пара методов, да.
Нет. Вы заменяете переменную-член и пару методов на другую переменную и пару других методов. Это, очевидно, эквивалентные наборы, и они не эквиваленты наборам «переменная» или «пара методов».
Вы заменяете переменную-член и пару методов на другую переменную и пару других методов.
Все верно, возможность такой замены — это именно то что я с самого начала и хотел показать.
А это не так, переменную можно заменить только другой переменной.
Вы путаете переменную и поле. Поле можно заменить парой методов и переменной (локальной, не полем), на которую будет сделано замыкание. Очевидно, что здесь использование переменной не должно являться чем-то дополнительным, т.к. это странно запрещать использовать локальные переменные в данном контексте.
В данном контексте странно умалчивать о наличии хранилища в правой части утверждения, так как в левой части речь идет о нем.
Ну ок, теперь понятно, только непонятно, что вы этим хотели доказать. Как это убирает проблемы множественного наследования? Как я понимаю, при такой схеме оно просто невозможно, но для его запрещения есть более простые средства.
«Не понимаю, почему они выбирают между OOP и процедурами? Можно же выбирать между OOP и HTTP»
Идеи ООП описаны для некого абстрактного понятия объекта (класса), но объекты бывают очень разные.
Например, доменные классы. Как их лучше описывать в ООП? Это просто иерархия структур данных или у них могут быть методы? Сколько раз пытался добавить к доменным классам поведение, каждый раз получалось плохо. Сторонние статические хелперы описывались гораздо проще.
Красивее всего с ООП получается писать нечто вроде графических редакторов. Там все как по книжке: базовые классы задают общую концепцию системы, затем они расширяются разными способами и реализуют все необходимое многобразие функций.
Самой загадочной и сложной для меня областью применения ООП являются всякого рода обрабатывающие информацию сервисы, которые должны принимать запросы, следить за изменениями внешних данных, анализировать, вычислять, отправлять ответы, формировать некие события, воздействия и т.д. Программа все время пытается превратиться в ком из каких-то менеджеров неопределенного назначения, тесно взаимодействующих с нарушением инкапсуляции и внутри построенных по сути на обычной процедурной концепции. Если в таких задачах попытаться создать более мелкие объекты и решать задачи за счет их взаимодействия, то для меня вообще все превращается в сущий ад.
Было бы очень интересно где-то прочитать про best practices, особенно по последней задаче.
Красивее всего с ООП получается писать нечто вроде графических редакторов.это пока вы не реализуете Undo/Redo и банальное сохранение.
Я думаю проблема в том, что undo/redo/save — это более низкий уровень абстракции по сравнению с основными функциями объекта в графическом редакторе. А поведение при редактировании — это общее поведение группы объектов а не отдельного объекта.
Лично я для undo/redo/save делал объекты не plain и все изменения проводил через отдельный менеджер. Поведение при редактировании реализовывал отдельными классами-обработчиками.
ООП в классическом виде хорошо работает для задач типа отображения на различных устройствах с общим базовым классом различных графических объектов, так же имеющих общего предка и прочие функции того же уровня абстракции.
При наивном восприятии идеи ООП объекты должны сами знать как им реагировать на те или иные ситуации при редактировании. В этом случае команды могут быть не обратимыми.
Простой пример: мы двигаем в редакторе элемент, который связан с другим элементом и они должны двигаться вместе. Команда знает о первом объекте, но не знает что попутно сдвинула и второй. Тогда undo команды может работать не правильно. Значит правила поведения при редактировании нельзя инкапсулировать в объекты.
Обработку движения элемента, связанного с другими решается и при наивном восприятии ООП, моделируя связи как свойство объектов, например.
Обще-философски для меня проще писать код в стиле «если графические объекты A,B,C попали в определенную ситуацию, то с ними нужно сделать следующее...» чем код в стиле «если я объект A и я попал в определенную ситуацию, то я должен сделать следующее...».
Второй вариант — это как заставить клеточный автомат сделать нечто нужное подбором правил. Очень сложно получить нужное глобальное поведение инкапсулированными в объектах а
В защиту ООП. 7 несостоятельных аргументов его противников