Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, Serginio1, Вы писали: S>>>> А что мешает это присобачить к структурам? S>>>Отсутствие у них VMT. S>> Еще раз наследование для структур это не VMT это наследование полей свойств и методов. S>>Это наследование без виртуальных методов. Для структур это запрещено! S>Какой сценарий в прикладном коде выиграет от наследования с такими ограничениями? S>Наследование ценно там, где экземпляр субтипа может выступать в качестве супертипа. S>А в нашем случае что? S>Вот у нас S>
S>public struct Name
S>{
S> public Name: string;
S>}
S>public class Person
S>{
S> public Name: Name;
S>}
S>
S>А теперь мы хотим применить наследование: S>
S>public struct FullName: Name
S>{
S> // Name: string is inherited
S> public LastName: string;
S>}
S>...
S>var p = new Person();
S>p.Name = new FullName {Name = "John", FullName = "Doe"}
S>
S>Что будет происходить? Молчаливое урезание FullName до Name? Ошибка компиляции? S>Единственное место, где FullName можно относительно безопасно использовать вместо Name — это ref Name аргумент в какую-нибудь функцию. Ну, или в ref-поле ref-структуры — то есть, опять же, конструкции, которая может жить исключительно на стеке. S>И то, непонятно, как GC будет отслеживать ссылки — ему ведь нужно как-то понимать, что в данном экземпляре Person через Name.LastName прикопана ссылка на строку.
Для структур такое присвоение запрещено, как и ref.
p.Name = new FullName {Name = "John", FullName = "Doe"}
В чем проблемы? Мы работаем не с объектами!
В конце концов это может быть агрегирование с SG с доступом к protected полям и свойствам!
и солнце б утром не вставало, когда бы не было меня
Многие концепции в ООП и ФП сходны, особенно это видно на гибридных (а не чистых функциональных) языках. Замыкание создает объект с полями, алгебраические типы и pattern matching напоминают наследование и т.д.
OCaml, Haskell, CLOS в Common Lisp так или иначе добавляют ООП к функциональной парадигме. Ключевым является только вопрос изменяемого состояния у объектов, и вот он решается по-разному.
Теория категорий, на которой базируется Haskell, определяет категорию как набор доменов (данных) и морфизмов (стрелок, связей, функций) между ними — это похоже на определение ООП по Алану Кею (существуют только объекты и сообщения между ними + скрытие состояния и познее связывание).
Я сейчас как раз пытаюсь самостоятельно реализовать ООП на Лиспе через замыкания и setq, но пока выходит криво и неудобно. Хотелось бы что-то вроде:
Это прямо как корпускулярно-волновой дуализм в физике. Тот, кто постигнет связь между ФП и ООП, дойдет до полного просветления в computer science. Невежды же считают, что верна только одна из этих парадигм.
— Нет в мире справедливости, — простонал Билл, когда цепкие пальцы Смертвича впились в его плечо.
— Конечно, нет, — согласился Смертвич. — А ты как думал?
Здравствуйте, Sinclair, Вы писали:
S>Принципиальное отличие в наличии изменяемого состояния, для которого необходима идентифицируемость (identity).
Имхо, неизменяемого состояния не достаточно. В ФП не менее важно, что данные являются самодостаточными, они не привязаны к методам. То есть функции отдельно, данные отдельно. Это сильно меняет способ декомпозиции. ФП программа выглядит иначе — обычно это пайплайн из функций, через который "прокачиваются" данные. Если сделать обычные классы, у которыйх все поля readonly — это все равно ООП программа. ФП появляется, когда есть пайплайн.
Здравствуйте, mrTwister, Вы писали: T>Имхо, неизменяемого состояния не достаточно. В ФП не менее важно, что данные являются самодостаточными, они не привязаны к методам. То есть функции отдельно, данные отдельно. Это сильно меняет способ декомпозиции. ФП программа выглядит иначе — обычно это пайплайн из функций, через который "прокачиваются" данные. Если сделать обычные классы, у которыйх все поля readonly — это все равно ООП программа. ФП появляется, когда есть пайплайн.
Ну, во-первых, если вы попробуете написать программу с "обычными классами, у которых все поля readonly", а также константами вместо всех локальных переменных, то вы неизбежно получите пайплайн (как мне кажется).
Во-вторых, для практического программирования иммутабельности, конечно же, недостаточно.
Например потому, что эмуляция ФП в виде readonly-классов заставит вручную выписывать все замыкания, что сделает код утомительно многословным.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Worminator X, Вы писали: WX>Я сейчас как раз пытаюсь самостоятельно реализовать ООП на Лиспе через замыкания и setq, но пока выходит криво и неудобно. Хотелось бы что-то вроде:
У нас в магистратуре в курсе лекций по Clojure кратко упоминалась возможность навелосипедить себе какое угодно ООП. С наследованием хоть интерфейсов, хоть реализации, хоть состояния.
С прототипным наследованием и с наследованием классов. С одиночным и множественным наследованием. С одинарной, двойной, и множественной диспетчеризацией.
Один из предлагаемых курсовиков, ЕМНИП, как раз был "реализуйте свой вариант ООП". Поскольку у лиспа синтаксиса никакого нет, то любой результат будет не хуже встроенных лисп-конструкций.
Подробностей я, к сожалению, не запомнил, но могу взять копию слайдов у лектора
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Ну, во-первых, если вы попробуете написать программу с "обычными классами, у которых все поля readonly", а также константами вместо всех локальных переменных, то вы неизбежно получите пайплайн (как мне кажется).
Не получится пайплайн, так как функции не будут друг другом стыковаться. Чтобы стыковались, надо отделять данные от функций, как в линке, где есть отдельно IEnumerable и отдельно набор extension методов над ним. ФП — это когда вся программа подобным образом устроена. При этом сильно меняется подход к декомпозиции. Забавно смотреть, когда берут обычный ООП код, внутри метода какого-нибудь класса используют паттерн-матчинг и думают, что это теперь такой ФП
Здравствуйте, Разраб, Вы писали:
Р>В чем принципиальное отличие?
Отличие в "парадигме". ))
В точке взгляда на проблему и в способах её решения.
ООП предлагает проектирование сверху, т.е. начиная от высокоуровневых абстракций, оставляя реализацию абстракций гибкой.
И так на каждом уровне иерархии.
Сокрытие состояний в ООП принципиально, т.к. помогает несовершенному человеческому моску абстрагироваться от частностей.
В ФП данные открыты, проектирование идёт от вычислений над открытыми данными.
Т.е. трансформация состояний в ФП явная, в отличие от ООП.
Почти всегда в ФП удобней проектировать снизу, через анализ операций над данными.
Ввиду того, что поддержка абстракций в ООП изначально была выше (и этот подход доказал своё право на жизнь в сложном ПО), в ФП тоже активно завозят различные методы абстрагирования, например, классы типов в Хаскель для кортежей операций, или категории данных — эдакая шаблонизация устройства структур.
Но без "окончательной" инкапсуляции (сокрытия) устройства данных, ФП всерьёз не выстрелит.
А стоит только пойти на это — и ФП превратится в ООП. ))
В любом случае, ООП-подход на сегодня самый толерантный, т.к. включил в себя все известные на сегодня парадигмы.
Например, наработки из ФП прекрасно используются в ООП для реализации алгоритмов над данными, т.е. при наполнении абстракций "мясом".
=============
А про изменяемость/неизменяемость, как тут некоторые пытаются озвучивать, это лишь составные части парадигм, которые могут быть присущи обоим подходам. Это уже частности, которые уместны для конкретных рассматриваемых алгоритмов над данными. Для одних ситуаций неизменяемость помогает, для других мешает.
Здравствуйте, Sinclair, Вы писали:
S>Поэтому ООП — это не столько про "объединение функций и данных", сколько про представление решения в виде объектов, изменяющих своё состояние во времени.
Повторяешь известные заблуждения, на которые здесь отвечали еще в нулевые. ))
Основные отличия на сегодня только в явности или неявности представления состояния.
В ООП и ФП происходит одно и то же: вот есть некое состояние S, есть вычисления над ним, есть новое состояние S'.
Разница в том, что в ФП обычно можно одновременно ссылаться как на старое состояние S, так и на новое S', т.е. уникальность сущности как бы "расплывается".
Но в ФП точно так же можно организовать ассоциацию на новое состояние, чтобы эмулировать identity из ООП.
И обратное тоже верно, например, COW или RCU — это использование практик из ФП в ООП, т.е. явное разделение состояний и одномоментная "транзакционная" подмена текущего состояния S новым вычисленным S'.
Т.е., наработки из обоих подходов успешно взаимно проникают и о серьёзном противопоставлении парадигм говорить на сегодня уже бессмысленно.
Здравствуйте, Sinclair, Вы писали:
S>Это позволяет делать не только многостадийную инициализацию в одну строку, но и описывать произвольные функциональные зависимости. S>Например, нетрудно строить всякие конвейеры в стиле S>
S>from d in _dates select d.AddDays(17).AddYears(-1);
S>
Кстате, реляционки — хороший показатель того, как ООП и ФП прекрасно уживаются совместно.
Т.е. никакого противопоставления там нет и близко.
Запросы, хранимки, таблицы и строки таблиц — это всё объекты базы, в лучших традициях ООП.
С т.з. пользователя, при изменении строки база не порождает копию строки, запросом изменяется именно некая "конкретная" строка (берём самый востребованный случай наличия уникального ключа).
Но "унутре" вовсю резвится ФП, т.к., в зависимости от степени изоляции, одни юзвери в тот же самый момент могут видеть как старую версию строки при пробеге по таблице (если взяли иммутабельный снапшот), так и новую (попросили динамический курсор).
Здравствуйте, mrTwister, Вы писали:
T>Не получится пайплайн, так как функции не будут друг другом стыковаться. Чтобы стыковались, надо отделять данные от функций, как в линке, где есть отдельно IEnumerable и отдельно набор extension методов над ним.
Не обязательно. Посмотрите, к примеру, как устроены DateTime / TimeSpan. Там нет никакого отделения данных от функций, а пайплайн — есть.
Точно так же можно вполне себе убрать Enumerable extension methods, перенеся их внутрь всех перечислимых классов.
Вот, к примеру, в тайпскрипте из коробки есть аналогичный линку пайплайн на массивах — инстанс-методы map, reduce, filter. (а для Iterable<T> — нету). Они как раз считают массив неизменяемым.
Именно так и будет выглядеть пайплайн, если заставить писать все классы immutable. В принципе не получится сделать void List<T>.Add(T item), только List<T> List<T>.Add(T item).
А, значит, можно будет написать
const a = List<int>.Empty.Add(4).Add(8).Add(15).Add(16).Add(23).Add(42);
И всё остальное точно так же будет стыковаться — просто List<T>.Filter(...) будет возвращать List<T>. А чтобы, скажем, стыковать разные типы коллекций, у List<T> будут методы .ToArray() и static FromArray().
Enumerable с его екстеншн-методами — просто способ обойтись без написания map Select/reduce GroupBy/filter Where в каждой коллекции.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Во-первых, речь шла не о дотнете, а о C++. Современные компиляторы C++ прекрасно устраняют лишние копирования в цепочках вроде m2 = m1.setField1(17).setField2(42). S>Во-вторых, в дотнете иммутабельность прекрасно дружит с value-типами. Где джит тоже устраняет лишние копирования в цепочках модификаций.
В ассемблере просто жесть. Почти 90% функций set_string1/set_string2/set_string3 занимает move всего этого огромного this по цепочке. Я не вижу оптимизации или можешь тогда показать что не так в коде, может я идею не уловил?
Здравствуйте, Sinclair, Вы писали:
S>Не обязательно. Посмотрите, к примеру, как устроены DateTime / TimeSpan. Там нет никакого отделения данных от функций, а пайплайн — есть.
Это не то совсем. В этом подходе список функций фиксирован и определяется разработчиком типа. Это нормально для библиотечного типа вроде DateTime, и не подходит для бизнес-логики. У тебя могут быть два независимых модуля: А и Б, у модуля А свои функции и пайплайн написанный в терминах этих функций, у модуля Б свои функции. И выход пайплайна модуля А передается на вход пайплайна модуля Б. В твоем подходе ты не сможешь таким образом декомпозировать код на независимые модули.
Здравствуйте, mrTwister, Вы писали:
T>Здравствуйте, Sinclair, Вы писали:
S>>Не обязательно. Посмотрите, к примеру, как устроены DateTime / TimeSpan. Там нет никакого отделения данных от функций, а пайплайн — есть.
T>Это не то совсем. В этом подходе список функций фиксирован и определяется разработчиком типа.
Внешние функции никто не отменял. Что мешает строить пайплайн на них?
T>И выход пайплайна модуля А передается на вход пайплайна модуля Б. В твоем подходе ты не сможешь таким образом декомпозировать код на независимые модули.
moduleAModel
.doModuleAStuff() // can be extension
.toModuleBModel() // extension
.doModuleBStuff() // can be extension
//etc
Здравствуйте, mrTwister, Вы писали:
T>Это не то совсем. В этом подходе список функций фиксирован и определяется разработчиком типа. Это нормально для библиотечного типа вроде DateTime, и не подходит для бизнес-логики. У тебя могут быть два независимых модуля: А и Б, у модуля А свои функции и пайплайн написанный в терминах этих функций, у модуля Б свои функции. И выход пайплайна модуля А передается на вход пайплайна модуля Б. В твоем подходе ты не сможешь таким образом декомпозировать код на независимые модули.
Если у вас функция из модуля Б принимает на вход тип из модуля А, то в любом случае модуль Б зависит от А. Не получится сделать его независимым ни в ФП, ни в ООП, ни в процедурном программировании.
Более реалистичные варианты:
1. Функция из модуля А возвращает какой-то тип, определённый в модуле Core. Например, Tuple<string, int>. Прекрасно работает в модуле Б, который вообще ничего не знает про модуль А, и, может быть, даже написан раньше.
2. Функция из модуля М склеивает выход функции из модуля А со входом функции из модуля Б при помощи переходной функции, написанной в модуле М. Ну, там List<T> toList(T[] array). Тоже всё работает и в ФП, и в ООП.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, GarryIV, Вы писали:
GIV>Внешние функции никто не отменял. Что мешает строить пайплайн на них?
Ничего не мешает, я же ровно это выше по треду и писал:
Чтобы стыковались, надо отделять данные от функций, как в линке, где есть отдельно IEnumerable и отдельно набор extension методов над ним. ФП — это когда вся программа подобным образом устроена.
Здравствуйте, Sinclair, Вы писали:
S>Если у вас функция из модуля Б принимает на вход тип из модуля А, то в любом случае модуль Б зависит от А. Не получится сделать его независимым ни в ФП, ни в ООП, ни в процедурном программировании. S>Более реалистичные варианты:
Все так.
S>1. Функция из модуля А возвращает какой-то тип, определённый в модуле Core. Например, Tuple<string, int>. Прекрасно работает в модуле Б, который вообще ничего не знает про модуль А, и, может быть, даже написан раньше.
Не обязательно Tuple, это могут бить бизнес-ориентированные типы объявленные в базовом модуле.
S>2. Функция из модуля М склеивает выход функции из модуля А со входом функции из модуля Б при помощи переходной функции, написанной в модуле М. Ну, там List<T> toList(T[] array). Тоже всё работает и в ФП, и в ООП.
Назвать этот подход "ООП" — это как каша из топора. От ООП там остался только язык программирования, созданный в эпоху расцвета идей ООП.
Здравствуйте, mrTwister, Вы писали:
T>Назвать этот подход "ООП" — это как каша из топора. От ООП там остался только язык программирования, созданный в эпоху расцвета идей ООП.
Ну так это и есть ФП, завелосипеденное из "ООП с ограничениями".
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Videoman, Вы писали:
V>В ассемблере просто жесть. Почти 90% функций set_string1/set_string2/set_string3 занимает move всего этого огромного this по цепочке. Я не вижу оптимизации или можешь тогда показать что не так в коде, может я идею не уловил?
Вообще, код самих функций неинтересен, т.к. они предназначены для инлайнинга, и в бою вызываться через call вообе не должны.
А main можно чуть улучшить, если внести тела функций внутрь определения класса. Это поможет инлайнингу, за которым, собственно, и должно последовать устранение временных копий. Ещё немножко эффективнее получается, если возвращать из set_stringX не obj, а obj&&.
Но даже после таких манипуляций на стеке остаётся три либо четыре временных объекта, и main превращается в перекидывание данных из одного в другой. Подозреваю, что вмешивается нетривиальная логика std::string.
Н-да, похоже, я переоценил мастерство современных компиляторов С++.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.