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

ООП: худшее, что случалось с программированием

Уровень сложностиСложный
Время на прочтение20 мин
Количество просмотров103K
Всего голосов 277: ↑151 и ↓126+56
Комментарии1118

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

ЗакрепленныеЗакреплённые комментарии

ваш код вообще неподдерживаем  ... что вы будете делать, если

Я уже от вас много раз слышал "а что будет если", и без проблем реализовывал ваши требования. Как я говорил это был последний раз.

Зря я, конечно, упрощенный пример вам написал

За несколько попыток не смогли придумать ничего сложнореализуемого на ФП, и дальше не придумаете.

вы все-равно почти используете ООП

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

Принципиальная разница - классы, которые:

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

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

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

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

Многие минусы современного ООП на классах можете еще раз перечитать в статье.

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

Использовать для чего? Какую задачу мы решаем?

Я могу привести миллион примеров, когда уместнее использовать Java/C# в сравнении с Go, и наоборот. Язык должна определять задача, а не чувство прекрасного случайных людей из интернета.

Я вижу, например, свою прелесть в КОБОЛе, но писать на нем веб я бы не стал. Мне импонирует раст, но не для крупных проектов, а для полировки узких мест (и я все равно выбираю чистый C). Julia могла бы стать прекрасной заменой питону в ML, но увы. Руби и отчасти перл — позволяют разработчику развлекаться от души, но потребители придумали рельсы, которые по сути погубили язык. Идрис — это хаскель, сделанный правильно (завтипы), — но для сообщества он оказался слишком сложным, и допилить компилятор они не могут уже сто лет. Я буквально вчера опубликовал заметку про то, как и зачем я использую пролог. Мне нравились Delphi (и некоторое время меня кормили). LISP — прекрасен, но даже человекопобные лиспы типа кложы и lfe — для средней команды разработки — чересчур.

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

Аналогично. Хайлоад (АБС крупного банка). Основной инструмент - RPG (альтернатива COBOL на платформе IBM i - те же задачи, но возможностей побольше и синтаксис в современном диалекте человеческий). Чисто процедурный язык. Синтаксически простой, но мощный и быстрый.

Плюс возможность собирать программу из кусков на разных языках. Так что где надо (и удобно) - можно и на С (край - С++) написать какие-то функции.

Язык должна определять задача

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

А что не нравится в Julia?

Мне-то как раз все нравится.

Меня особенно впечатляет mmap

Рельсы думал это венец творения в руби, а оказывается погубили его

Перетянули одеяло. Теперь у большинства Руби ассоциируется с Рельсами. И основная разработка ведётся вокруг них.

К экосистеме эрланга этот давно питает нежную любовь, но применить пока нигде не получается… )

А вы Erlang не упомянули.

Как не упомянул? Упомянул, конечно.

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

Как вариант бэк на Go.

Для максимальной скорости - C.

Но согласен, что идеального пока нет. Нужно его сделать.

На том же TypeScript прекрасно пишется […] и серверный бэк, и десктопы. 

Уточнение: серверный бэк для трёх с половиной калек в месяц и десктопы для людей, у которых в ноуте 64G оперативы.

Да и там не потянет... Если вдруг понадобится обработка сотен миллионов записей. И побыстрее.

Нет, скорей всего вы пишите в VS Code или ваши коллеги, который прекрасно работает на JS быстрее чем продукты JvmBrains.

Я работал в нем не так давно на своем стареньком макбуке с 8Гб памяти, параллельно запуская симулятор или эмулятор мобилки.

По поводу бэка - на NodeJS одни из самых производительных приложений (конечно если не сравнивать с C/Rust и тп), используются в AWS, Netflix, Paypal, геймдеве и много где еще. И уж куда производительнее Java (писал в той же статье про многопоточку и очереди).

В вашей статье про многопоточку и очереди я не нашёл ни слова про System.Threading.Channels , что выдаёт уровень экспертизы.

И нафига мне Channels если я разбирал аналог стандартных примитивов типа Monitor? Нашелся "эксперд".

Могу добавить в бенчмарк ПР с каналами ради интереса, но сам писать не буду.

И обеспечение ими линеаризуемости ,что самое важное. Гарантии линеаризуемости

Нет, скорей всего вы пишите в VS Code или ваши коллеги, который прекрасно работает на JS быстрее чем продукты JvmBrains.

Не сразу понял, что у вас JvmBrains это JetBrains (на опечатку не похоже). Я пользуюсь и тем и тем, и VSCode всё же уступает. К тому же там поддержка некоторых языков и фреймворков сильно хуже.

Автор 1 раз в статье и один раз (минимум) в комментах написал JvmBrains.

При этом статья в хабе Java, но про Джаву ни слова (C# & TS), кроме дописывания до кучи в списке других языков пару раз.

При этом какие то статьи по Джаве еще есть.

При этом сравнивает JetBrains и другие IDE для Джавы не сравнивая мега разницу в возможностях...

Выше пишет, что Нода "уж куда производительнее Java".

Складывается впечатление, что автор не сильно знает Джаву. Просто дописал до кучи.

П.С. По самой статье: все что из нее нужно вынести - не нужно быть адептами никаких сект и слепо верить догмам. Нужно выбирать из всех возможностей и парадигм что есть. Выбирать комфортную вам и вашей команде под задачу и ресурсы. И быть гибкими в своем выборе.

Автор явно старается и ищет упорно и активно свой идеал. И он молодец в этом поиске. Видно, что много читает и интересуется. В общем путь идет достойно. Тут вопросов нет.

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

Джет Брейнс лучший инструмент. Но он уступает в одном и самом важном. Лучшую поддержку использования возможностей современных LLM. А это критическое превосходство. Поэтому придется заплатить за Курсор, и просто переключаться между IDE когда понадобятся инфраструктурные правки

Никогда не мог понять, что такого хорошего в JetBrains, и зачем нужны современные LLM в разработке. Прекрасно себя чувствую, ни о чем не жалею.

По мне, так современные LLM это не то, что должно использоваться в разработке. ИМХО, это должна быть более надёжная система, а нынешние системы ещё в зародышевым состоянии. Это выглядит как откручивание винта ножом.

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

Поддерживаю. А ещё использование LLM отучает думать. Но это уже реальность (((

Подтвержаю, вот в Пейпал, а заодно рендеринг Нетфликса и Шопифая держат всего-то по четыре пользователя.

Да там скорее всего как в Facebook - формально используется php, а фактически он транслируется в код на C++, который потом компилируется.

Нет, они даже статьи писали что после переписывания на TS с джавы работать все стало в 10 раз быстрее при меньшем количестве кода, и разработка быстрее при меньшем количестве разрабов.

Это может означать только то, MVP на джаве писали три с половиной джуна по методу ad-hoc-ad-hoc-и-в-продакшн, а переписывали с учетом всех набитых шишек — нанятые на уже прибыльный продукт профи.

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

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

Откройте для себя https://github.com/akka/akka

А то вы так говорите, как будто в любом окружении, где можно запустить JS/TS, — многопоточность нормальная. Если вам кажется, что нода или браузеры умеют в многопоточность — это просто означает, что многопоточность вам не нужна в принципе. Ну, может, 10-20 потоков — максимум.

И да, кстати, что вы называете многопоточностью? Параллелизм или асинхронность?

Это означает, что код на Яве писали люди с недостаточной квалификацией, которые не знали характерные идиомы. Ява используется аж в алготрейдинге. Ява была разработана для Сановских серваков, которые лет 15 назад перешли на совершенно видеокартную многопоточность — под 256 потоков исполнения.

10 раз — это либо переход от наивной виртуальной машины без JIT к компилируемому языку (по опыту OCaml, где есть 2 кодогенератора), либо более оптимизированный исходный код.

С учётом того, что Java выполняется как раз с JIT, то остаётся только алгоритмическая оптимизация. То есть, если бы переписали с Java на Java, получили бы то же самое.

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

Посмотрел бы на этот алготрейдинг, если в моём алготрейдинге на каждый бранч смотрят как на источник вариабельной (и плохой) латентности, а там целый GC/JIT/непонятночто.

Ява была разработана для Сановских серваков, которые лет 15 назад перешли на совершенно видеокартную многопоточность — под 256 потоков исполнения.

И по похожим причинам мой алготрейдинг по факту однопоточный.

Посмотрел бы на этот алготрейдинг,

Ну понятно всё с ними, разумеется. Но тем не менее, если они до сих пор не разорились (UBS), значит как-то оно работает.

И по похожим причинам мой алготрейдинг по факту однопоточный.

Это две разные задачи.

Я к тому, что на простом перекладывании JSON'ов переходом с Java-на-что-то без конкретного переписывания алгоритмов, десятку производительности не получишь.

Это может означать только то, MVP на джаве писали три с половиной джуна по методу ad-hoc-ad-hoc-и-в-продакшн, а переписывали с учетом всех набитых шишек — нанятые на уже прибыльный продукт профи.

Вы сейчас описали практически всю J2EE, лол.

Есессно.

Да я читал про этот инструмент. Идея проксирования на более низкоуровневые языки широко используется всеми. И в целом даёт существенное превосходство.

Голословно конечно можно говорить что серверный TypeScript для калек, но что-то больше напоминает брюзжание и юношеский максимализм.

Как и десктопы, так то есть целые быстрые и шустрые редакторы кода с полным фаршем и не требующие особо железа.

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

больше напоминает брюзжание и юношеский максимализм

Не просто напоминает, а по факту таковым и является

JS уже перестал для отрисовки UI на десктопах по сути подымать оверхед в виде инстанса браузера для отрисовки "веб-приложения", коим весь JS и является? Спасибо хоть часть фреймворков не таскает с собой целый монструозный хромиум, как это делает электрон.

Я писал для десктопа на JS до того как появился электрон, ещё в 2009. Но уверен что можно было и ранее. Просто там открывался текущий системный браузер. Впрочем, однажды я писал для встраиваемой Opera, у которой было вырезано всё лишнее. Уже чуть больше весило, но всё же. Под Windows XP нормально было. А сейчас есть платформы, которые также открывают системное. Да чего уж там - Telegram с mini-apps, там тоже браузер с некоторым набором пробрасываемых данных.

Электрон и NW конечно толстоваты, но там бизнес задача была с написанием так чтобы работало везде и можно было скачать на любую платформу и чтобы работало одинаково, а окружение зафиксировано в стиле Docker - внутри. Да, такое требует много памяти, но вроде нынче память сильно дешевле. И по факту чтобы сделать вот очень качественно - надо делать действительно качественно и тогда электрон работает вполне себе хорошо, больше ест, но не тормозит. А вот когда делают в стиле главное побыстрее и так сойдет, либо если в разработке корпоративный ад с бюрократией и квадратичными алгоритмами... тут всё что угодно тормозит, просто на электроне это заметно значительно сильнее.

JS уже перестал для отрисовки UI на десктопах по сути подымать оверхед в виде инстанса браузера для отрисовки "веб-приложения", коим весь JS и является? 

Ну типа. См. например tauri - он не грузит отдельный инстанс браузера, а использует системный WebView2, который шарится между всеми приложениями в системе, которым он нужен. MS свой Teams на подобное перевели, например.

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

Можно было карму и не минусовать 🙃

Неужели под HTA писали? Или под Sciter? Я думал я тут один дед-пердед, кто все это помнит :)

P.S. я не минусовал

Нечто похожее, только изначально там конструктор UI для десктопа со своим языком и прочими плюшками, и встроенной IDE. И бонусом веб, куда можно было прокидывать биндинг на внешнюю функциональность. Ну и те же VBScript и JScript, так что HTA, но протюненый. Делали, как ни странно, испанцы, называлось NeoBook. Сейчас вроде оно как-то даже живо, но теперь там вроде ближе к электрону и вообще забвение. А вот 16 лет назад нормально было.

Ещё меня однажды звали писать UI для теликов, в 2013, тогда там обычно был браузер с кастомными html и расширенными привилегиями для JS.

Вообще мир веба значительно больше чем некоторым кажется. Хотя пик развития в 2014 примерно был, сейчас нового на порядки меньше, хоть и бывает.

производительность бэка это больше результат правильной архитектуры и правильного разделения read/write моделей и баз данных под них нежели результат выбора технологии.

Да ну? А что, если у меня на бэке нет базы данных?

Значит с высокой долей вероятности вы изобрели велосипед.

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

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

Несомненно.

А что у вас есть?

Ну in-memory cache, например. Добываем данные из message broker’а и предоставляем их в режиме реального времени другим частям системы.

Мне кажется, говорить о предпочитаемых языках можно начинать примерно с того момента, когда способен писать работоспособный код примерно на 10-20 других.

Иначе получается, что «джава прекрасна, потому что я раньше писал на ассемблере».

Согласен, и я десяток знаю.

Как студенты? Или использовали в продуктиве на протяжении нескольких лет? Я вот активно пишу на С++ и питоне уже больше 10 лет и не могу с уверенностью сказать что знаю их, а вот только выйдя из универа у меня в резюме стояло знание тоже примерно десятка языков. Просто судя по статье вы ооп не знаете, но умудряетесь критиковать его, может тоже самое ис языками?

За слова про "ООП не знаете" ответить можешь? С примерами кода обязательно.

Ну это как бы моё мнение, так что да отвечаю, я и правда думаю что вы не знаете ООП

Вот яркий пример

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

Вы взяли пример для обучения и сказали, смотрите всё работает шикарно и читаемо. А вот пример из жизни, вам нужно написать систему плагинов для своего продукта (возьмём например Maya) это подразумевает что пользовател должен иметь возможность написать свой класс и все системы должны с ним работать корректно, предлагаете пользователю пересобирать исходники программы для которой он пишет расширение? Искать во всех местах свитчи и добавлять свой тип? ООП это в первую очередь про архитектуру и про управление сложностью, где важно знать где создавать ограничения, а где создавать возможность для расширения. Ваш первый же пример.

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

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

Очередной демагог, тебя просили примеры кода, а их нет. Где код который нужно написать на ФП?

ок вот примитивный код

void Viewport::mouseClick(mouseEventData data)
{
    auto active_tool = ToolManager::getActiveTool();
    if (active_tool)
        active_tool->processPick(render->getScreenContext(), data.x, data.y);
}

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

const mouseClick = (data: MouseEventData) => {
  processPick(getActiveTool(), getScreenContext(), data.x, data.y)
}

И?

Вы React'теру хотите доказать что клик не обработать в ФП?))))

А что внутри processPick? тул то пользователтский, а пользователь менять код processPick не может, или там перегрузка? И что возвращает getActiveTool?

// пример пользовательского кода
class SomeNewTool : public SomeTool
{
public:
    void processPick(ScreenContext& context, int x, int y) override 
    {
        SomeTool::processPick(context, x, y);
        Allign(selectedItems());
    }
};

import {Tool, align, processPick} from 'some-library'

export type CustomTool = Tool & {
  ... // some custom fields
}

export const customProcessPick = (tool: CustomTool, context: ScreenContext, x: number, y: number) => {
  processPick(tool, context, x, y)
  
  align(tool.selectedItems)
}

Реализовать можно много как, и зависит от библиотеки конечно же. Либо она на базовых типах, либо вообще на обобщениях, что еще гибче. И в целом задачу хотелось бы увидеть, а не догадываться.

А что внутри processPick? И что возвращает getActiveTool?

Откуда мне знать что внутри кода из вашей головы? Возвращает какой то текущий тул? Вы мне расскажите.

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

По прежнему не увидел содержимого processPick, а customProcessPick как будет вызываться? Выше у вас processPick зовётся, каким образом он вызовет customProcessPick? а внутри у вас снова вызывается processPick бесконечная рекурсия?

Откуда мне знать что внутри кода из вашей головы? Возвращает какой то текущий тул? Вы мне расскажите.

У меня очевидно возвращается указатель на какой-нибудь ITool который является интерфейсом.

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

Но вы так и не написали, ну точнее написали но непонятно как оно работает.

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

https://tinyurl.com/2xkh2tvv

Напишите ваше решение в вашем стиле без ООП

Talk is cheap, show me the code.

Есть практики, а есть теоретики. В теории земля плоская и небо твердое.

Ну видимо наш теоретик решил слиться, что странно он ведь так рьяно просил код, а когда дело дошло до действий, то код уже не так уж и нужен, да @gen1lee?

type Tool = 'collect-in-radius' | 'align' | 'instancer'

const collectInRadius = (x: number, y: number) => console.log('From ToolCollectInRadius')
const align = (x: number, y: number) => {
  collectInRadius(x, y)
  console.log('From ToolAlign')
}
const instancer = (x: number, y: number) => console.log('From ToolInstancer')

const tools: Record<Tool, (x: number, y: number) => void> = {
  'collect-in-radius': collectInRadius,
  align,
  instancer,
}

const processClick = (tool: Tool, x: number, y: number) => {
  tools[tool]?.(x, y)
}

const main = () => {
  processClick('collect-in-radius', 10, 10)
  processClick('align', 10, 10)
  processClick('instancer', 10, 10)
}

main()

В коде много какого то неиспользуемого мусора. Вот код что выводит то же самое.

Если нужна доп логика, допиши в вывод в консоль, либо тесты.

Нет, этот код не является эквивалентным, у collectInRadius есть состояние радиуса, которое для каждого тула который унаследован от него может быть разным, align использует результат collectInRadius который является локальным только для этой тулы. Я думал это очевидно что состояния у каждой тулы своё и хотел увидеть как именно это вы решаете видимо вам явно надо это показать.

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

Я думал

Думал но не сказал, молодец. Код выводит то же самое, если нужно состояние очевидно нужно было использовать состояние в выводе, чтобы потом не писать что ты думал?

код сразу же проспится если

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

Лишний мусор только просьба удалить и сделать его минималистичным.

Я так понимаю вы слились? Я так и думал.

Код
type CollectState = {
  radius: number
  items: number[]
}

type AlignState = CollectState

type InstancerState = {
  items: string[]
}

type State = {
  activeTool?: string
  toolStates: Record<
    string,
    | ReturnType<typeof collect.createState>
    | ReturnType<typeof align.createState>
    | ReturnType<typeof instancer.createState>
  >
}

const collectItems = (items: number[], radius: number) => {
  for (let i = 0; i < radius; i++) {
    items.push(i)
  }
  console.log(`\tadded items in radius ${radius}`)
}

const collect = {
  createState: (): CollectState & {type: 'collect'} => ({items: [], radius: 0, type: 'collect'}),
  processPick: ({items, radius}: CollectState) => {
    console.log('From ToolCollectInRadius')
    collectItems(items, radius)
    console.log('\t', items.join(','))
  },
  update: (state: CollectState, i: number) => {
    state.radius += i
  },
}

const align = {
  ...collect,
  createState: (): AlignState & {type: 'align'} => ({items: [], radius: 0, type: 'align'}),
  processPick({items, radius}: AlignState) {
    console.log('From ToolAllign')
    collectItems(items, radius)
    console.log(`\talign ${items.length} items`)
  },
}

const instancer = {
  createState: (): InstancerState & {type: 'instancer'} => ({items: [], type: 'instancer'}),
  processPick({items}: InstancerState) {
    console.log('From ToolInstancer')
    items.forEach((item) => console.log(`\tspawn item: ${item}`))
  },
  update({items}: InstancerState, i: number) {
    items.push(i.toString())
  },
}

const state: State = {
  toolStates: {},
}

const processClick = () => {
  if (!state.activeTool) return

  const toolState = state.toolStates[state.activeTool]
  switch (toolState.type) {
    case 'align':
      return align.processPick(toolState)
    case 'collect':
      return collect.processPick(toolState)
    case 'instancer':
      return instancer.processPick(toolState)
  }
}

const updateTool = (i: number) => {
  if (!state.activeTool) return

  const toolState = state.toolStates[state.activeTool]
  switch (toolState.type) {
    case 'align':
      return align.update(toolState, i)
    case 'collect':
      return collect.update(toolState, i)
    case 'instancer':
      return instancer.update(toolState, i)
  }
}

state.toolStates.collectInRadius = collect.createState()
state.toolStates.align = align.createState()

state.activeTool = 'collectInRadius'
updateTool(8)
processClick()

state.activeTool = 'align'
updateTool(15)
processClick()

state.toolStates.instancer = instancer.createState()

state.activeTool = 'instancer'
updateTool(10)
processClick()

state.activeTool = 'collectInRadius'
processClick()

state.activeTool = 'instancer'
updateTool(100)
processClick()

Playground.
Даже без замыканий, чистый процедурный стиль, полностью типизирован. В полтора раза меньше строчек чем у вас, без классов.

На замыканиях еще проще и компактнее можно.

О супер! Вы я надеюсь уверенны в своём коде.

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

Вот что напишет пользователь моей системы:

class ToolRemoveInRadius : public ToolCollectInRadius
{
public:
    void processPick(int x, int y) override 
    {
        std::cout << "From ToolAllign\n";
        collectItems();
        for(auto& item : selectedItems())
          std::cout << "\titem removed: " << item << '\n';
    }
};
// регистрация
ToolManager::instance().regesterTool<ToolRemoveInRadius>("remove_in_radius");

Что должен написать пользователь вашей системы?

Напоминаю, вам нельзя менять код редактора, то есть то что вы выше написали, менять нельзя. Это к вопросу о переиспользуемости кода. Как думаете сработает? Я вот сильно сомневаюсь.

В результате вы написали код который нельзя переиспользовать, вы дублируете объявление items: [], radius: 0 и передаёте их напрямую в collectItems (а представьте если бы там было намного больше параметров, ведь в реальности это именно так и происходит, какой-нибудь наследник от QPushButton).

Дополнительно помимо того что вы должны каким-то образом узнать какие функции нужно перегружать (в случае С++ сам компилятор подскажет) вы ещё должны и структуры правильно объявить, где тут DRY и простота?

По поводу больше\меньше кода. Ваши две функции:

const updateTool = (i: number) => {
  if (!state.activeTool) return

  const toolState = state.toolStates[state.activeTool]
  switch (toolState.type) {
    case 'align':
      return align.update(toolState, i)
    case 'collect':
      return collect.update(toolState, i)
    case 'instancer':
      return instancer.update(toolState, i)
  }
}

const processClick = () => {
  if (!state.activeTool) return

  const toolState = state.toolStates[state.activeTool]
  switch (toolState.type) {
    case 'align':
      return align.processPick(toolState)
    case 'collect':
      return collect.processPick(toolState)
    case 'instancer':
      return instancer.processPick(toolState)
  }
}

против моих

void processClick(int x, int y)
{
    if (auto active_tool = ToolManager::instance().getActiveTool())
        active_tool->processPick(x, y);
}

void update_tool(int i)
{
    if (auto active_tool = ToolManager::instance().getActiveTool())
        active_tool->update(i);
}

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

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

Вы правда думаете что ваше решение лучше?

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

Я лишь в очередной раз выкинул весь ненужный мусор что у вас был. Если нужно регистрация сторонних инструментов, то нужно вернуть registerTool (Playground), и делать через обобщения, что даже было указано в статье:

Код с регистрацией инструментов через обобщения
type CollectState = { radius: number, items: number[] }
type InstancerState = { items: string[] }
type Tool<TState> = {
  createInitialState: () => TState
  processPick: (state: TState) => void
  update: (state: TState, i: number) => void
}

const collectItems = (items: number[], radius: number) => {
  for (let i = 0; i < radius; i++) {
    items.push(i)
  }
  console.log(`\tadded items in radius ${radius}`)
}

const collect: Tool<CollectState> = {
  createInitialState: () => ({items: [], radius: 0}),
  processPick: ({items, radius}) => {
    console.log('From ToolCollectInRadius')
    collectItems(items, radius)
    console.log('\t', items.join(','))
  },
  update: (state, i: number) => {
    state.radius += i
  },
}

const align: Tool<CollectState> = {
  ...collect,
  processPick({items, radius}) {
    console.log('From ToolAllign')
    collectItems(items, radius)
    console.log(`\talign ${items.length} items`)
  },
}

const instancer: Tool<InstancerState> = {
  createInitialState: () => ({items: []}),
  processPick({items}) {
    console.log('From ToolInstancer')
    items.forEach((item) => console.log(`\tspawn item: ${item}`))
  },
  update({items}, i: number) {
    items.push(i.toString())
  },
}

const state: {
  activeTool?: string
  tools: Record<string, { tool: Tool<any>, state: any }>
} = {
  tools: {}
}

const registerTool = <T,>(key: string, tool: Tool<T>) => {
  state.tools[key] = { tool, state: tool.createInitialState() }
}

const setActiveToolKey = (key: string) => {
  if (!state.tools[key]) return
  state.activeTool = key
}

const processClick = () => {
  const {activeTool, tools} = state
  if (!activeTool || !tools[activeTool]) return

  tools[activeTool].tool.processPick(tools[activeTool].state)
}

const updateTool = (i: number) => {
  const {activeTool, tools} = state
  if (!activeTool || !tools[activeTool]) return

  tools[activeTool].tool.update(tools[activeTool].state, i)
}

registerTool("collectInRadius", collect)
registerTool("align", align)
registerTool("instancer", instancer)

setActiveToolKey('collectInRadius')
updateTool(8)
processClick()

setActiveToolKey('align')
updateTool(15)
processClick()

setActiveToolKey('instancer')
updateTool(10)
processClick()

setActiveToolKey('collectInRadius')
processClick()

setActiveToolKey('instancer')
updateTool(100)
processClick()

Что должен написать пользователь вашей системы?

Как думаете сработает? Я вот сильно сомневаюсь.

Дополнительно помимо того что вы должны каким-то образом узнать какие функции нужно перегружать (в случае С++ сам компилятор подскажет)

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

По поводу больше\меньше кода. Ваши две функции

На обобщениях получилось еще лучше. Всего 99 строчек кода.

Вы правда думаете что ваше решение лучше?

Уверен что лучше. А вы?

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

registerTool("custom", {
  createInitialState: () => ({
    ...collect.createInitialState(),
    instancer: instancer.createInitialState(),
    instancer2: instancer.createInitialState(),
  }),
  processPick: (state) => {
    console.log('From Custom')
    collect.processPick(state)
    collect.processPick(state)
    align.processPick(state)
    instancer.processPick(state.instancer)
    instancer.processPick(state.instancer2)
  },
  update: (state, i) => {
    collect.update(state, i)
    collect.update(state, i)
    instancer.update(state.instancer, i)
    instancer.update(state.instancer2, i)
  }
})
setActiveToolKey('custom')
updateTool(15)
processClick()

Я лишь в очередной раз выкинул весь ненужный мусор что у вас был.

Ну вы же не тупой в отличии от 90% программистов должны же понимать что код написан для потенциального использования другим человеком и как концепт.

Вы слишком много сомневаетесь, просто не разбираетесь в ФП.

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

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

ну теперь то да.

На обобщениях получилось еще лучше. Всего 99 строчек кода.

Всё ещё больше моего варианта в вашем стиле. Ну и более чем уверен что больше по количеству символов.

Уверен что лучше. А вы?

А я уверен что ваше хуже. потому что по-прежнему никак не мешает сделать так

state.activeTool = 'some_invalid_name'

И всё сломать. Ну и в принципе играть с чужими стейтами и всё поломать.

А ещё у вас any имеется и как там с типобезопасностью?

Вот кстати и от меня задачка,

Изи

struct Custom : public ToolAllign{
        ToolInstancer m_instancer;
        ToolInstancer m_instancer2;
        void processPick(int x, int y) override {
            std::cout << "From Custom\n";
            ToolCollectInRadius::processPick(x, y);
            ToolAllign::processPick(x, y);
            m_instancer.processPick(x, y);
            m_instancer2.processPick(x, y);
        }
        void update(int i) override {
            ToolCollectInRadius::update(i);
            m_instancer.update(i);
            m_instancer2.update(i);
        }    
    };
    ToolManager::instance().regesterTool<Custom>("Custom");    
    ToolManager::instance().setActiveTool("Custom");
    update_tool(15);
    processClick(10, 10);

И даже меньше строчек чем у вас, да и код меньше и проще.

у вас вполне процедурный код

это в процедурном то коде создаются функции первого класса, да еще и с замыканиями изменяемыми потенциально?

Всё ещё больше моего варианта в вашем стиле

А код можно в студию? или это тот что был без registerTool и тп?

по-прежнему никак не мешает сделать так

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

any имеется и как там с типобезопасностью

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

И даже меньше строчек чем у вас, да и код меньше и проще.

Код меньше и проще?) Сам себя не похвалишь, как говорится..

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

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

это в процедурном то коде создаются функции первого класса, да еще и с замыканиями изменяемыми потенциально?

ну вашем коде выше этого всего нет.

А код можно в студию? или это тот что был без registerTool и тп?

ниже michael_v89 привёл код к вашему стилю.

Он не мешает потому что ...

А вот это ещё один минус вашего подхода, как потом использовать тул из других файлов? Как мне получить активный тул? Представте например что функция updateTool находится в другом файле. Ну и это получается своего рода инкапсуляция против которой вы так выступали.

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

да что вы, а не потому ли вы удалили код, который просили написать в ооп напомню:

А теперь внесем пару небольших правок (Playground):

type InstancerState = { items: number[], color: number }

processPick: (state) => {
  console.log('From Custom')
  collect.processPick(state)
  align.processPick(state)
  instancer.processPick(state)
},
update: (state, i) => {
  collect.update(state, i)
  instancer.update(state, i)
}

потому что вы облажались и instancer использовал items от collect? код. Очень типобезопасно лол.

Код меньше и проще?) Сам себя не похвалишь, как говорится..

так вы сами так делаете постоянно, лол или в своём глазу бревна не видите?

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

Забавно но бесполезно. Мой класс также можно впихнуть в main (что я собственно и сделал) и получить приватный тип.

По поводу задачи - вы доказывали что мол в ПП/ФП нельзя сделать так, как в ООП - я вам доказал что можно.

Компиль упаси, я не собирался доказывать что так нельзя сделать, это можно сделать в любой парадигме. Вопрос был в том где это будет сделать удобнее, быстрее, и проще поддерживать и расширять. И как мне кажется мой вариант получше вашего будет, ибо меньше возможностей накосячить (забавно что именно вы это продемонстрировали), меньше писать приходится (сравните по символам а не по строчкам если сомневаетесь) и проще читать. Ну и стоит учитывать что вы активно используете методы, против которых воюете (log, push, join). Как по мне я выиграл этот спор, ну по голосам за коменты тоже.

ниже michael_v89 привёл код к вашему стилю.

он многое удалил, а ваш код намного больше

А вот это ещё один минус вашего подхода, как потом использовать тул из других файлов? Как мне получить активный тул? Представте например что функция updateTool находится в другом файле.

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

да что вы, а не потому ли вы удалили код, который просили написать в ооп напомню:

потому что вы облажались и instancer использовал items от collect? Очень типобезопасно лол.

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

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

Код на с++ проще читать и писать ))) А на руках ходить проще чем на ногах. По поводу сравнить по символам - так сравните сами, что то мне подсказывает что ваше решение и здесь проиграет. Можно устроить тут борьбу за каждый символ, а можно прочитать статью еще раз и убедиться что если код без классов как минимум не хуже даже на примерах, специально сделанных ООП-шником, чем код с классами, то использовать классы это говнокод.

что вы активно используете методы, против которых воюете (log, push, join)

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

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

facepalm

он многое удалил, а ваш код намного больше

Ну да он удалил много пробелов + то что вы вообще даже и не писали, так что, без разницы.

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

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

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

Нуда ну да, и вообще вы белый и пушистый, нет вы облажались, потому что исходный инстансер работал со строками и в принципе у него другая логика работы была, collect.processPick(state) влияет на работу instancer.processPick(state) а не должен ибо у них нет пересекающихся отношений, то что вы сделали как раз и показывает отсутствие типобезопасности.

Код на с++ проще читать и писать ))) 

Конкретно в данном случае, да. Что забавно )

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

Так в том то и дело что он хуже, помимо типа вам нужно таскать стэйт, да и ещё и контролировать что стейты таскаются именно те что нужно, вы в своём же примере накосячили. Писать кода у вас больше нужно, и это с примитивным примером, в реальности полей у классов гораздо, гораздо больше, да и методов. И что вы все поля будете прокидывать через аргументы и инициализировать в каждом конструкторе? И это пример с минимальным наследованием. Вы же не тупой как 90% программистов, подумайте о сложности скалирования вашего решения. Да и стэйт вы по сути передаёте как this только явно и не типобезопасно.

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

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

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

Ну супер, а потом сопостовлять стэйт и функции которые нужны?

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

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

вы в своём же примере накосячили

Посмотрите на мое сообщение что вы сами скинули - я как раз меняю тип с массива строк на массив чисел. Так что никаких косяков и проблем с типобезопасностью там нет вообще. Боитесь признать что в вашем любимом С++ такое невозможно сделать?)

Вы используете объект с методами, чтобы зарегистрировать тулу. Это же ООП.

Понятие ООП я довольно четко прописал в статье. Классы не используются.

А в коде используется просто словарь с функциями.

«Словарь с функциями» и есть объект, который позволяет вам реализовать полиморфизм.

Так классы - это и есть словарь с функциями (virtual table). Просто удобно реализовано на уровне языка.

а классы типов или трейты еще удобнее!

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

Да кстати, забыл добавить, что произойдёт с вашей системой если сделать например так

state.activeTool = 'some_invalid_name'

В моём случае просто вернётся фолз и активным останется тот тул что был, это к вопросу что у вас меньше кода, вы просто не весь функционал реализовали )) ну и форматирование у нас отличается чутка.

В полтора раза меньше строчек чем у вас

Неа. У него открывающая фигурная скобка на отдельной строке, а у вас на той же. У него между объявлением методов пустые строки, а у вас нет. Функция main у вас не объявлена. allign у него отдельная функция, а вы просто код скопипастили. У него объявлена функция selectedItems, а у вас нет.
То есть вы просто сжульничали.

У него есть #include, а у вас все встроенные функции глобально доступны. У него items выводятся по отдельности, что занимает дополнительные строки, а вы вызвали стороннюю функцию items.join(','), между прочим в объектном стиле. Аналогично с items.forEach(). У вас нет обработки ситуации, когда в state.activeTool задано название несуществующего инструмента.

Все отличия не связаны с использованием ООП для кодеков.

https://www.mycompiler.io/view/LZT9EFBmlWr
Вот его код, написанный полностью аналогично вашему. На 20 строк меньше чем у вас, к тому же там 6 строк на #include.

без классов

Зато с объектами такой же структуры, с дублированием структуры в типе. Тип+объект это аналог класса+new, createState это аналог констурктора, const align = { ...collect, ... } это аналог наследования. Всё как в ООП, только императивным кодом и более многословно.

Копипаста switch (toolState.type) 2 раза, и так будет в каждом новом методе. items и radius дублируются в типе и в инициализации. Инициализация для CollectState и AlignState дублируется. А если полей будет штук 10, то надо будет переносить, и разница в количестве строк будет еще больше.

Ваш код это отличный образец говнокода. Оставляйте так, это весьма наглядно показывает в чём ООП лучше. Для тех кто способен понять вообще.

(это автору статьи, если что)

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

@Psychosynthesis Переписал вод все требования, см. ответ выше.

@michael_v89и получилось еще меньше, и ничего себе - даже без switch и дублирования инициализации.

@Chaos_Optimaи даже setActiveTool вернул с проверкой.

и получилось еще меньше

Если сделать одинаковое форматирование и не считать include, то количество строк будет такое же, как в моем варианте на C++. Вот diff отличий.

Diff
- virtual ~ITool() = default;    
- ITool* getActiveTool() {
-   if(m_tool_builders.find(m_active_tool_name) == m_tool_builders.end()) return nullptr;
-   return m_tool_builders[m_active_tool_name];
- }

+ tools: {}
+ }
+ ...collect,
+ createInitialState: () => TState
+ createInitialState: () => ({items: [], radius: 0}),
+ createInitialState: () => ({items: []}),

В коде на C++ есть getActiveTool, которая связана не с использованием ООП, а с особенностями работы std::map. А у вас все дополнительные строки связаны с вашим подходом.

В остальном коде есть около 30 строк (30% кода), которые более многословны по сравнению с аналогичными строками в C++, и содержат копипасту. Которую надо будет везде менять при переименовании или добавлении работы с новыми полями. То есть пока что у вашего кода не видно никаких преимуществ.

Diff
- struct ITool
+ type Tool<TState>

- struct ToolCollectInRadius : public ITool
+ type CollectState = { ... }
+ const collect: Tool<CollectState>

- struct ToolAllign : public ToolCollectInRadius
+ const align: Tool<CollectState>
+ ...collect

- struct ToolInstancer : public ITool
+ type InstancerState = { ... }
+ const instancer: Tool<InstancerState>


- virtual void processPick(...)
+ processPick: (state: TState, ...) => void

- void processPick(...) override
+ processPick: ({items, radius}, ...)

- void processPick(...) override
+ processPick({items}, ...)


- virtual void update(int i)
+ update: (state: TState, i: number) => void

- void update(int i) override
+ update: (state, i: number)

- void update(int i) override
+ update({items}, i: number)


- virtual void collectItems()
+ const collectItems = (items: number[], radius: number)

- collectItems()
+ collectItems(items, radius)


- std::map<std::string, ITool*> m_tool_builders
+ tools: Record<string, { tool: Tool<any>, state: any }>

- template<class T> void registerTool(const std::string& tool_name)
+ const registerTool = <T,>(key: string, tool: Tool<T>)

- m_tool_builders[tool_name] = new T()
+ state.tools[key] = { tool, state: tool.createInitialState() }


- active_tool->processPick(...)
+ tools[activeTool].tool.processPick(tools[activeTool].state, ...)

- active_tool->update(i)
+ tools[activeTool].tool.update(tools[activeTool].state, i)

Вы код сам скиньте, что сравниваете, а то непонятно о чем речь.

А вот пример из жизни, вам нужно написать систему плагинов для своего продукта […]

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

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

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

К сожалению, мне известно только одно значение слова «Тула» (город такой под Москвой), но попробую ответить (это псевдокод).

plugins()
|> filter(selected_object)
|> reduce({selected_object, settings}, fn
  plugin, acc -> plugin.apply(acc)
end)

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

class InstancerTool : public ITool
{
public:
    void processPick(ScreenContext& context, int x, int y) override 
    {
        auto world_pos = context.getSorldPosition(x, y);
        SpawnNewItems(world_pos);
    }
};

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

Вам интересно было посмотреть? — Я показал. Вы не поняли? — бывает.

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

Хорошо, постараюсь расписать. У нас есть например игровой редактор с вьюпортом на котором отрисовываются игровые объекты. И есть панель инструментов, пользователи редактора могут расширять функционал инструментов (тулов) добавляя свои инструменты. Выше два примера, один инструмент выравнивает объекты в радиусе от клика (выделение объектов в радиусе реализовано уже в базовом классе SomeTool), второй инструмент создаёт новые объекты относительно координаты. Все настройки инструментов инкапсулироованны в самих инструментах. И представьте что подобных инструментов сотни (вообще их количество не детерминировано), и их поведение может отличатся кардинально. Как это реализовать в ФП учитывая что инструмент имеет изменяемое состояние, и в процессе использования может менять состояние чего угодно (в рамках предоставленного АПИ редактора)

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

Потом вы выбираем по типу выделенного объекта те инструменты, которые умеют с ним работать (filter в моем коде выше) и применяем их один за другим (если их несколько) — это reduce.

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

Абсолютно совершенно так же как работают любые микросервисные системы.

Вместо спагетти из наследуемых классов, вместе меняющих shared state - детерминированный АПИ системы плагинов, который получает от плагина команды и выполняет их.

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

Ну поднимать микросервисы ради такого, это мощно, и конечно же это будет работать быстрее и это будет проще поддерживать, правда ведь, правда?

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

Выше по ссылке я накидал пример минут за 10-15 сколько вам придётся потратить времени чтобы сделать тоже самое на микросервисах?

Насколько удобней будет пользователю расширять это? В моём решении всего лишь унаследоваться от интерфейса и переопределить метод.

Микросервисы мощный и очень полезный инструмент, но нужен ли он тут? Я ещё не видел 3D редакторов которые использовали бы микросервисы для плагинов.

Не "сделать на микросервисах", а "работать будет так же как микросервисы". Передача сообщений будет например через Го каналы. Или через вызов функции с паттерном observer.

class Tool t m where
  processPick :: t → Ctx → Point → m ()
  handleSelection :: t → [Object] → m ()

data BackdoorCCP = BackdoorCCP
  { address :: String
  , port :: Int
  }

instance Tool BackdoorSendToCCPServers IO where
  processPick bd _ p = sendToCCP bd $ "User clicked at " <> p
  handleSelection = ...

data FilterClicks base = FilterClicks
  { filterPred :: Ctx → Point → Bool
  , baseTool :: base
  }

instance Tool t m => Tool (FilterClicks t) m where
  processPick FilterClicks{..} ctx p
    | filterPred ctx p = processPick baseTool ctx p
    | otherwise = pure ()
  handleSelection = ...

data TestSpy = TestSpy

instance Tool TestSpy (WriterT [(Point)] m) where
  processPick _ _ p = tell [p]
  handleSelection = ...

Как один из кучи вариантов.

Честно говоря ничего не понял, можно комментарии написать? (в фп языках вообще не силён) это клиентский код или код редактора, или и то и то?

Часть — код редактора (который определяет тайпкласс Tool,и непоказанная часть по использованию, потому что грузить экзистенциалами читателя не стоит). Часть — код клиентов (определяющих конкретные тулы, это каждая пара data/instance).

Простите но всё равно не могу осилить код, слишком инопланетный для меня.

слишком инопланетный для меня

Это было понятно с самого начала... Увы, чтобы понять ответ, надо знать хотя бы часть его.

@IUIUIUIUIUIUIUI - это к нашей дискуссии про нужность высшей математики и образования вообще. Вот гражданин, образованный от С++ до С++. Ну и что ты ему объяснишь?

Пока у него не будет определённой базы, он даже не поймёт твои объяснения, считая их полной чепухой. Задав свой вопрос он не знает половины ответа => полный ответ пролетает мимо ушей.

Я никогда не говорил, что математика не нужна для постижения X или Y. Я говорил (и говорю, и буду говорить), что математическое образование вне чуть-более-чем-школьного не нужно для ≈99% программистских задач и вакансий.

А в данном конкретном случае — ну не понял чего-то человек, ну и что? Разве он будет от этого хуже жить? Меньше есть? Чаще просыпаться по ночам? Станет ли он менее счастливым?

Нет.

А в данном конкретном случае — ну не понял чего-то человек, ну и что?

"Одна ошибка, и ты ошибся" :-) Конечно ничего. Просто беседовать с ним бессмысленно.

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

я вот тоже не силен в вашем языке, но мне тут видится ООП - есть и класс и его инстансы, с переопределенными методами.

Можете по простому объяснить - чем это от явы отличается? кроме синтаксиса

мне тут видится ООП

А его нет.

Можете по простому объяснить — чем это от явы отличается?

Нет «объектов» и «методов», вместо них — отдельно голые данные и отдельно функции, не привязанные к ним. Это очень упрощенно, и местами не совсем верно, но ровно как вы просили.

Это тоже ООП только с системой типов без подтипирования =)

Там тонкие различия. Так-то с высоты птичьего полёта вся CompSci - это ну возятся люди с текстами и компьютерами, кнопки нажимают.

В классах типов, к примеру, вы можете иметь сразу 2 аналога self:

class Add a where
   add :: a -> a -> a

То есть, у вас функция 2 параметров одного типа a. Вне функциональщины это мультиметоды LISP/Julia.

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

Это потому, что вы кроме ООП, увы, ничего больше не знаете. Это классы типов, на языке Java - это нечто, похожее на интерфейсы.

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

Вы таки не поверите, но плагины отлично пишутся на С без всяких классов. Посмотрите код таких проектов как PostgreSQL или SQLite. А "С с классами" позволяет писать плагины кодерам, кто программировать (то есть архитектуру планировать) не умеет.

С примерами кода обязательно

Чтобы было видно преимущества ООП проект должен быть большим. Тридцатистрочники, как в вашей статье, вообще в принципе без разницы на чем писать, а вот когда дело доходит до больших проектов, то большинство почему-то оказываются написаны с использованием ООП, исключая, может быть числодробилки. Интересно, в чем может быть причина, лично я думаю, что это из-за того, что все вокруг тупые, и не имеют 13 лет опыта коммерческой разработки, иной причины мне трудно себе представить, ведь не может же ООП просто лучше подходить для таких проектов -- это совершенно исключено :)

P.S. За примерами соответственно можете пройти в гитлаб любого крупного проекта на мультипарадигмном языке, например Unity.

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

оно как раз на С написано

С использованием ООП.

Почему-то адепты С++ до сих пор ничего похожего написать не смогли

Qt, gcc, KDE смотрят с недоумением на эту чепуху.

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

Отличное сравнение. Глючные, тормозные и прожорливые Qt и KDE против быстрого и надежного ядра линукс, которое обеспечивает работу почти всех серверов в мире, а также многих мобильников и десктопов. Попробуйте КДЕ на rpi zero 2 запустить - притом, что последнее ядро линукс (и еще уйма софта, включая ресурсоемкую обработку видео) там работают замечательно. Это только для фанатов ООП считается нормальным, что отрисовка кнопочки с прозрачностью требует на порядки больше ресурсов, чем обработка потокового видео высокого разрешения :)

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

https://lwn.net/Articles/444910/

Глючные, тормозные и прожорливые Qt и KDE

Детсадовский наброс, старайтесь лучше.

быстрого и надежного ядра линукс

Быстрое - да. Надёжное - нет. По количеству уязвимостей, по крайней мере.

Ну и монолитное ядро с надёжностью плохо пересекается в принципе...

А про gcc совсем ничего придумать не смогли? Ну вы постарайтесь, что ли.

фанатов ООП

Пока что фанаты тут только с обратной стороны отметились.

gcc вроде на чистом C написан.

Уже лет 10 как на С++ переведён, если не больше.

А про gcc совсем ничего придумать не смогли? Ну вы постарайтесь, что ли.

Компиляторы крайне неудобно писать на языках без ADT и сборщика мусора. Поэтому спор C vs C++ тут пролетает - оба хуже.

А что даёт сборщик мусора при написании компилятора?

Возможность не думать о высвобождении памяти, циклических ссылках и т.д.

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

Запустил gcc под профайлером памяти на TUшке, которая компилируется пару минут и жрёт при этом гигов восемь памяти в пике — чуть больше терабайта аллокаций.

Не, память освобождать надо.

Компиляторы крайне неудобно писать на языках без ADT и сборщика мусора

Проблема на вашей стороне.

Нет, проблема объективно существует. В тех же gcc и clang те же ADT переизобретены явно, насколько это можно сделать в C++.

ADT в Шланге называются ASTMatchers. И сборщик мусора тоже где-то там торчит, но где — я не знаю.

Это вы теперь будете утверждать, что любой большой/успешный проект использует ООП, просто потому, что для вас ООП это не технологии, а показатель размера/успеха?

Детсадовский наброс, старайтесь лучше.

Если вы не можете запустить КДЕ на rpi zero 2 и сравнить его отзывчивость с шеллом, то стоит ли комментировать? Абсолютно очевидно, что КДЕ непрерывно лезет в своп даже при наличии целых полгига оперативки. Я разработчикам КДЕ в свое время предлагал помощь в оптимизации (аконади), с подробным разбором, где они налажали и как именно починить, но это никому не интересно. Обсуждение можно нагуглить, весьма показательно. Что интересно, SQLite, HAProxy и многие другие проекты помощь принимают, можете найти мои тикеты и патчи, а вот участники секты ООП плевать хотели на качество и производительность.

Ну и монолитное ядро с надёжностью плохо пересекается в принципе...

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

А про gcc совсем ничего придумать не смогли?

Больше сказать нечего? Расскажите нам, что странного, что компилятор С++ написан на С++. А когда в основном компилировали им С, то и компилятор был на С. Погуглите хоть историю проекта, прежде чем глупости постить.

В С нет ООП

Ну совершенно не обязательно должны быть языковая поддержка ООП, чтобы писать в таком стиле. Посмотрите, скажем, на glib2.

Знаете поговорку "программист на ФОРТРАНе на любом языке может писать как на ФОРТРАНе". Я своими глазами видел, как такой программист пишет по-английски как на ФОРТРАНе. :-)

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

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

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

Например, будет доступ к полю по ссылке, а потом это поле будет куда-то передано. В Rust мешает borrow-checker, а тут — политика конторы.

Карма отрицательная, отвечаю крайне редко.

Интересно же, почему?

Но согласен, что идеального пока нет. Нужно его сделать.

На том же TypeScript прекрасно пишется и веб, и мобилки, и серверный бэк, и десктопы.

"Один пацан писал всё на JavaScript, и клиент, и сервер, говорил, что нравится, удобно, читабельно. Потом его в дурку забрали, конечно." ©

А что собственно плохого? Я например прекрасно понимаю что api лучше писать скажем на go, ещё большей производительности можно добиться на более низкоуровневых языках, есть бенчмарки, это всем известно. Вот у нас в компании api пишут на go и не применяют orm, пишут sql для производительности, работает хорошо и быстро. Полтора года назад решили поднять сервис, там и фронт и бэк (несколько) с бд разными и брокером. Так вот в одного разработчика на фронт и api все ушло около месяца. Фронт на vue, api nestjs с orm и вообще всем что ускоряет и упрощает разработку. Релизы, довольно функциональные, пишутся за пару дней - неделю. И того стоимость сервиса месяц ЗП одного разработчика. Я прекрасно понимаю что если сделать все хорошо и идеально на правильных инструментах работать оно будет сильно быстрее. Но какая бизнесу разница, если на сервере нагрузка по итогу с 10% на самой дешёвой тачке. А клиенты есть и уже год платят деньги. А все хорошо, идеально и на правильных инструментах, это найми фронта, найми бекендера с отдельным стеком, наладь синхронизацию между ними, пиши sql чистые под каждый кейс и ещё много всего. Рентабельность такого решения выйдет на порядок хуже. В заключение, это я все к тому что писать все на js конечно не надо, но кейсы когда это банально сильно выгоднее бизнесу есть.

Короче, статья - это пиар любимых языков автора.

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

Например недавно я сделал небольшой виджет для сайта, где ООП вписалось как родное.
Получилось 2 класса

class Service implements ServiceType {
  constructor(appId: string)
  async getSettings()
  async sendForm(payload: FormPayload)
  terminateAllRequests()
}

class Widget {
  constructor(service: ServiceType)
  render(container: HTMLElement)
  destroy()
}

// используется примерно как
const container = document.createElement('div')
document.body.appendChild(container)

const widget = new Widget(new Service('123'))
widget.render(container)

// ... когда виджет больше не нужен, деинициализиуем его
widget.destroy()
container.remove()

Widget рендерит внутри указанного элемента небольшое Preact приложение и через контекст предоставляет доступ к Service всем компонентам.
Я не слишком люблю наследование (хотя в отдельных случаях оно удобно), да и большие иерархии классов для меня выглядят подозрительно (для такого должна быть хорошая причина и довольно часто это поддержка легаси кода). Но как минимум если классы довольно просты и четко разделяют ответственность - это удобно.

Да, хуки не идеальны, колбэки боль и лишнии ререндеры случаются. С другой стороны при использовании бест практис и аккуратной реализации длинных листингов проблем с дерградацией производительности из за ререндеров обычно не бывает даже на больших страницах, даже на бюджетных телефонах (это из моего опыта, у вас может быть другой).
Если же проблемы все же есть, проблемные места обычно можно дополнительно оптимизировать с useReducer, useMemo, useRef и т.д.
Я как то привык к тому как выглядят компоненты в функциональном стиле и на фоне вышеприведенных аргументов на классы переучиваться неохота.
У хуков кстати есть классная возможность - можно писать свои кастомные хуки и удобно переиспользовать их где хочется. Не уверен, что это так же удобно делать на классах.
А если вот хочется сделать очень быстрое приложение, мало весящее, быстро рендерящее - тогда имеет смысл смотреть на альтернативы реакту вроде SolidJS (ну или прям на ваниллу)

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

На Solid, Svelte и тому подобные хайповые темы лучше, конечно, даже не смотреть, как, впрочем, и на React.

Ну так тот же самый класс можно использовать и с функциональными компонентами, никто же не мешает. Я частенько таким и пользуюсь, добавляя внешние зависимости для приложения.
Мы же вроде о преимуществе классов в роли компонентов react? Или я что то перепутал читая статью по ссылке?
А на тему хайпа и Solid/Svelte/React - по мне так фундаментальная смена подхода к написанию уже достаточно быстрого приложения (см выше мои аргументы), для оптимизации его производительности выглядит неубедительно
Вы кстати забыли про упомянутую мной ванилу - там скорость и возвращение к корням, вы же не посчитаете эту рекомендацию очередным происком хайперов?
Кстати потыкал по ссылкам, не уверен насчет вас, но я писал приложения и на Svelte и на Solidjs и там порой такая нелогичная фигня что мозг кипит. Но пару небольших приложений на Solidjs я все же сделал, размер конечного бандла многое окупает.

Вы перескочили на хуки - я пояснил вам и про них

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

Про ваниллу можете глянуть, например, тут.

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

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

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

Вы выставили хуки, как преимущество функций, чем они не являются.

Цитируя свое первое сообщение "хуки не идеальны"

Опять цитирую его же:

"У хуков кстати есть классная возможность - можно писать свои кастомные хуки и удобно переиспользовать их где хочется. Не уверен, что это так же удобно делать на классах. "

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

А являются ли хуки преимуществом функции или нет я даже обсуждать не хочу, это тема для холивара

На тему же бенчмарка и "все просто", мне вот просто удивительно, а что, фреймворки на каком то эльфийском написаны а не на vanillajs?

В том-то и дело, что функционал не уникальный, а вот недостатки вполне себе уникальные.

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

Vanilla предполагает написание прикладного кода без фреймворка.

Вообще то не понял.

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

Я не так знаком с классовыми компонентами в реакте как вы и хотел бы узнать, как выделить логику работающую с состоянием в отдельные модули по аналогии с кастомными хуками.

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

Vanilla предполагает написание прикладного кода без фреймворка.

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

В ОРП это делается так.

Это конечно, рекомендовать Go, в котором все элементы ооп используются кроме виртуальных функций. Даже наследование эмулируется за счет косвенной вложенности. То, чтот вы не модеже разобраться с конструкторами и вирткальными методами и вам надо явно прописывание вызовов это просто ваш skill issue, ООП тут не причем.

Если TypeScript это еще та штучка, а Хаскель не предлагать, тогда предложу PureScript. Клон Хаскеля с некоторыми улучшениями, который транспилируется в JavaScript (прямо как TypeScript). Да, библиотека скудная, но всегда можно подключить любую JS библиотеку, сделав минимальную обёртку. Т.е. есть библиотеки на все случаи жизни.

Хаскель не предлагать как язык широкого применения не проходит

Почему не подходит? Вполне применяю его широко, от написания на нём компиляторов до перекладывающих жсоны опердней.

Clojure - если прямо совсем хотеть в ФП, кстати, не такая маргинальная история, основная история успеха nubank, супер большой латиноамериканский банк > 1000 микросервисов. Но работы на нем не так много, как хотелось бы. Если будет интересно посмотреть, то тут парень снимает отличные видосы про него.

Менее идеальный, но сильно более популярный вариант Go.

Явно нужен какой то новый язык

Зачем? И так уже столько языков. Мне нравится Kotlin — мощный язык, почти универсального применения, с поддержкой ООП и ФП

Я использую только процедурное программирование.

Очевидно Haskell 😎

elixir, scala, например.

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

Я вот делаю электронные девайсы (измерительные приборы, очистители, анализаторы и т.д.), пишу на Си для АРМ архитектур. И меня бесит, что всякие умники пишут библиотеки для разных чипов на С++, создавая при этом один класс и пару/тройку объектов класса. А я потом за этим мудачьем переписываю их "продвинутый", сука, код на Си!!!!

Ненавижу С++ !!!!

Потому что он для написания прошивок, уважаемые звёздные инженеры на github, на хуй не нужен !!!!!!!!

И меня бесит, что всякие умники пишут библиотеки для разных чипов на С++, создавая при этом один класс и пару/тройку объектов класса.

Вообще печально, что в embedded, чисто как в уебе, «есть только один язык». Чистого С там точно не хватает, но Хруст туда уже явно не лезет... :-(

Я вот делаю электронные девайсы (измерительные приборы, очистители, анализаторы и т.д.), пишу на Си для АРМ архитектур.

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

Почему один? Есть еще Haskell ;)

Форк ajhc под эмбеддед благополучно заглох =(

Обычный GHC без форков, пишем для MCU от 8К памяти, можно и меньше

Здорово! Было бы интересно почитать статью об этом опыте

Scala - отличный язык: хочешь объектов с наследованием - пожалуйста, хочешь функционально - пожалуйста, хочешь смешать всё это вместе - пожалуйста.

Не, ну не худшее… худшее — это таки «it's a doll inside a doll inside a doooooooolllllll», типа атомов на электронах или курсора, половину проца сжирающего на его мигание (потому что каждый раз надо дёрнуть рефреши во всей матрёшке).

Вот это — да, это клиника. А ООП — так, мелкая попытка выдумывать новые сущности там, где это не особо-то и нужно было.

Я тоже пытался пользоваться — запутывает больше, чем упорядочивает. Да, соблазнительно привязать исполнимый код к его данным и получить в одном месте и структуру, и её функции (ну то есть «методы», один хрен). А потом вдруг оказывается необходимо две структуры вместе проанализировать на взаимные зависимости — и нам уже нужно нечто, обрабатывающее сразу две их. А потом вдруг оказывается, что их бывает от 1 до N.

И городушка наследования всего этого получается такая, что резко сливает классическим «сям» — есть структуры а, бэ, цэ и функции от (а), от (бэ), от (цэ) и куча новых функций вплоть до требуемой от (a[N], b[N], c[N]), всё просто и понятно. В голове держишь матан, а не вот это «хрустальное здание леса», где там что кто у кого наследует.

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

Да, примерно похожее пытался сформулировать.

Есть 100500 бизнес-процессов. Все они так или иначе занимаются обработкой неких потоков данных (в данном случае обычно это выборка из БД). И для каждого процесса своя выборка по своему набору таблиц (а их в системе тысяч 30 разных...). Тут вообще ничего общего, что можно было объединить в какой-то "базовый класс".

И да, есть сущности. Но. Этих сущностей достаточно много. И каждая сущность будет иметь 100500 свойств и методов. И при этом нет ни одного бизнес-процесса, который использовал бы их все. Обычно используются единицы из них. Но в каждом процессе свои. Опять писать базовые классы на 10 страниц каждый?

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

Так что я бы не сказал что ООП однозначно хорошо или однозначно плохо. Оно может быть уместно здесь и сейчас или неуместно. Соответственно, нужна свобода выбора - использовать или не использовать.

Давно я не видел такого перетягивания каната под своим камментом — рейтинг таскают туда-сюда с таааакой энергией… О_о

Кстати, идея: кроме рейтинга, сделать значок с верёвочкой, который ставится, если каммент совсем уж нешуточно перетягивали туда-сюда.

Еще нужно бэйджи придумать для постов.

Тут бы подошел например с горящей жеппой ) Автор поста определенно очень талантлив в подпале пердаков 😁

Были ж недавно :) Не взлетело, хотя, конечно, дьявол кроется в деталях и возможна более полезная реализация этой концепции…

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

А зачем вы используете наследование там где не нужно? Удивляет меня точка зрения "У нас ООП, значит надо обязательно куда-нибудь впихнуть наследование" :) Если в коде нет подходящего места для применения наследования, то не надо его применять. "ООП" это объектно-приентированное программирование, а не классово-ориентированное, поэтому код должен работать в первую очередь с объектами. А уж как вы повторяющиеся части объектов организуете, это другой вопрос - через наследование, композицию или еще как-то. Да, многие считают, что так использовать ООП неправильно, но при этом от "правильного" использования почему-то получают проблемы.

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

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

Переиспользование с другими типами

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

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

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

ps. и ещё про многопоточность интересно... ну да, разделю Ваше мнение о Ц и Го.. синхронизация мьютексы и прочее в целом не смешнго, гед бы они ни были, сообщения, каналы, наблюдатели - реализуются в любом стиле и языке, пусть и не болт-ин по паттернам.

Вы написали 42 функции умеющие работать с 7-ю разными типами данных.

Через 12 лет в ваш код потребовалось добавить 8-й тип данных.

Ваш "потомок" благополучно модифицировал 40 функций. Еще 2 он не обнаружил.

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

Думаете это умозрительный пример? Вот только что закончил возиться с подобной проблемой. Чтобы разобраться с задачей которая развивалась более 49 лет пришлось переписать ее на другой язык.

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

Функциональное программирование более гибкое, соглашусь. Податливо, как пластилин. Очень удобно для научных задачек и сольного программирования. НО ЗАВОДЫ ИЗ ПЛАСТИЛИНА НА СТРОЯТ.

А с чего бы он не обнаружил еще 2? TypeScript - куда более строгий язык чем Java или C#, если не использовать any (аналог Object в Java), то компилятор все подсветит. Даже если в switch не обработать все ситуации, или в union type появился новый тип, который не обрабатывается.

Про заводы - сейчас большинство сайтов написано на React в ФП. В тч самых сложных.

А ядро Linux - тоже не завод?

Слушай, ну вот в ядре линукс чистой воды ооп, на opaque и void* с ручными vtable (хотя именно это иногда как раз очень удобно). Просто там неявный this заменён на явный.

Отсутствие классов и неявный this на явный это и есть ФП.

- Какой функциональный язык вам больше всего нравится?

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

Это о каком Паскале речь?

Но это всё не отменяет, что архитектура GStreamer, FFmpeg, GLib/GTK, ядро Linux как раз ООП.

Как это реализовано: это уже детали. Вы же говорите вообще про ООП, что оно плохо. А тут вполне себе оно и есть. По факту.

И ФП... вы как-то резко пошли против принципа наименьшего удивления и применили термин "функциональное программирование" к тому, что обычно именуется "процедурное программирование".

Именно, по сути структура с указателями на функции является декларацией интерфейса, и через такой подход реализуется полиморфизм.

Функциональное программирование более гибкое, соглашусь. Податливо, как пластилин. Очень удобно для научных задачек и сольного программирования. НО ЗАВОДЫ ИЗ ПЛАСТИЛИНА НА СТРОЯТ.

WhatsApp — недостаточно завод?

Эрланг object-oriented в понимании Алана Кея. Так-то там типов данных всего восемь, и самый навороченный из них — map, а свои создавать нельзя.

Ваш "потомок" благополучно модифицировал 40 функций. Еще 2 он не обнаружил.

Где в это время был тайпчекер?

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

Функциональное программирование более гибкое, соглашусь. Податливо, как пластилин. Очень удобно для научных задачек и сольного программирования. НО ЗАВОДЫ ИЗ ПЛАСТИЛИНА НА СТРОЯТ.

Ага. Именно поэтому верифицированное ядро SeL4 в первую очередь сделано на хаскеле (с полуэкстракцией в C). Или поэтому смарт-контракты с требованиями доверия им делаются на хаскеле и верифицируются на коке/изабелле/етц.

Чё за изабелла? Я, кажется, что-то пропустил.

isabelle/hol

Мне не зашло, но люди пользуются (а меня и от кока чё-т тошнит).

Ага, спасибо.

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

Кок — говно, да.

а меня и от кока чё-т тошнит

Почему? Там же теоремки сильно проще чем в агде получаются

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

За счёт того, что в агде паттерн-матчинг сделан ну вот прям в определении функций, там это всё куда лучше выглядит.

Что, конечно, не отменяет куда более лучшей автоматизации в коке, поэтому имеем то, что имеем.

Lean4 видели? Лучше агды в плане автоматизации (и не только). Не могу сравнить с .. rocq.

Да (правда, относительно давно). Мне не понравилось что-то в его теории типов — если я правильно помню, там есть Prop, для которого работает uniqueness of identity proofs (что ломает совместимость с HoTT — один минус), но при этом он не импредикативный (для несовместимой с HoTT теории это минус).

Prop там импредикативный. В проекте ground_zero ("Lean4 HoTT library") вместо встроенного `=` объявили свой Id который сделали `Type`. Конечно, не факт, что это эргономично, не знаю.

Надо снова посмотреть, значит, спасибо за напоминание!

Спасибо за Lean4.

Холивары наше все.

Для каждой задачи подходит лучше определенный стек, но нет языка решающего... Легко решающего любые задачи, всегда какие то минусы. Поэтому выбирается язык оптимальный в плане большинства задач и удобный большинству разрабов. Все же знают почему пиндосы на маках работают в основном? Так и тут - массы обучены попсе и двигают ее не желая изучать альтернативы или не имея возможности убедить команду. Яркий пример про раст в линуксе.

Чем ругаться и отстаивать - создали бы список задач и стека под них с приоритетами, что бы по таблице можно было выбрать, понять где будет просадка, что вынуть в микросервисы и реализовать на другом языке

Это был бы труд для потомков и современников. А этот срач к истине не приведет

Если задаче 49 лет - ее еще лет 20 назад нужно было отрефакторить и переписать. Как минимум по соображениям ИБ и отсутствию поддержки новых инструкций процессоров и многопоточности. Ставьте реальные цифры что бы не получать такие замечания.

Легаси возрастом в десятилетия - это как раз реальность. Эти теоретические "нужно было переписать" разбиваются о подсчёт затрат на переписывание.

Легаси возрастом в десятилетия - это как раз реальность. Теоретические "нужно было переписать" разбиваются о подсчёт затрат на переписывание.

5 летние разбиваются, но такой древний код выиграет и по производительности и по ИБ

...но это не точно

/s По оценкам - точно

(ежегодно смету переписывать - лет пять до следующей итерации протянем)

Вы с такими идеями сходите в крупнейшие авиакомпании и банки США, посмотрим что вам скажут. :) Там 40-летний легаси с которым работают через 5 слоев эмуляторов терминалов это абсолютная норма.

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

А это проблема работяг или руководителей?

Сразу видно, с вас не трясли деньги. Причем и бизнес, и IT. Можно сколько угодно убеждать в важности задачи, но если денег нет, то тут или закрывать тему, или менять работу.

Я знаю что такое бюджет, целесообразность и прочее. И если речь про код 2005-2010 года - скорее всего нецелесообразно. Но ТС сам упомянул 49 лет, а это на минуточку код 76 года. Поверьте - если он не соврал то компания тратит огромные усилия что бы "это" вообще запускалось и уже что раз дешевле было переписать. Например до 90х годов дата элементарно не позволяла перейти в 21 век, так как экономили каждый байт Как решили интересно, и за сколько.

Но учитывая что ТС не сообщил ни язык, ни платформу (может спец военная\космо техника и Go\Rast не понимает) - скорее всего в его 49 годах лишняя вторая цифра.

А я смотрю вы один из тех кто вообще не понимает что переписать 40 лет отлаженного и стабильно работающего кода банковского ядра даже для компании из SP100 по цене, сложности и количеству простоя и убытков из-за неминуемых последующих багов и ошибок практически равноценно слову "невозможно".

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

А я смотрю вы из тех кто писатель, но не читатель, а мои аргументы почему нужно переписать даже если работает - не увидели. Остается надеяться что "вырастИте" когда нибудь вы и перестанете заниматься троллингом и переходить на личности, сводя дискуссию к тупому срачу.

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

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

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

Так ведь по заголовку функции должно быть понятно, что ей надо давать. Разве нет? Или предполагается подсовывать функции int, float, string в разных комбинациях и надеяться что функция выдаст правильный ответ?

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

Даже создатель Java вскоре признал, что добавление классов было ошибкой.

Пруфы-то будут? Было дело, что Тони Хоар признавал, что введение нулл-ссылок, а не классов, было "ошибкой на миллиард долларов".

Оригинальная цитата звучит так:

These lasses yesterday were of a null use.

В сети очень много статей находится по его (James Gosling) ответу на вопрос на какой то конференции:

If you could do Java over again, what would you change?

I'd leave out classes

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

сети очень много статей

Так приведите хоть одну.

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

А я вот поискал.

Some languages, like Go, leave out classes and inheritance, while others experiment with features like traits in Rust. As a language designer, what do you think is a modern, general-purpose, reasonable way for a programming language to do composition?

James: I don't think I wouldn't do classes. I actually find that classes work pretty well for composition. I don't really have any good, clear ideas for what to do differently. And some of the things that I would do differently are a little strange.

И еще

What would you do differently if you had to re-develop Java?

It's nothing. Yes, I know Java has changed a lot since its inception, but I love these changes. The language has developed and become absolutely amazing, and I feel like a proud father to him.

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

Ну ок, вот моя ссылка.

Древний сайт, на котором вроде как впервые появилась эта информация уже недоступен.

Под старость лет люди частенько начинают нахваливать себя в интервью и считать себя великими.

То есть вы на серьезных щщах выдаете шутку(!) из воспоминаний(!) об IRL встрече за пруф того, что Гослинг раскаивается? Там буквально в следующем предложении идет пояснение - на этом месте на вас избирательная слепота напала?

Под старость лет люди частенько начинают нахваливать себя в интервью и считать себя великими.

Да я вижу, что кому-то 13 лет в разработке уже достаточно, чтобы булшит нести.

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

А без наследования классы - вообще непонятно зачем нужны.

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

И дальше пошло типичное переобувание

Прям как у вас в это ветке, да?

Но факт

Я факта не увидел. Какой-то человек что-то там вспоминает. Для контраста оригинальные интервью я привел выше.

он так сказал, пусть и смягчил позже

Если он скажет, что был на Луне, а потом "смягчит", что на самом деле не был, вы тоже этим "фактом" будете размахивать?

А без наследования классы - вообще непонятно зачем нужны.

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

И по вашей же ссылки раскрывается мысль, что наследование инетрфейсов предпочтительнее наследования классов, а не "добавление классов было ошибкой."

someone asked him: “If you could do Java over again, what would you change?” “I’d leave out classes,” he replied. After the laughter died down, he explained that the real problem wasn’t classes per se, but rather implementation inheritance (the extends relationship). Interface inheritance (the implements relationship) is preferable. You should avoid implementation inheritance whenever possible.

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

Самое интересное, что null не был ошибкой! В контексте всего остального языка без него никак. Монада Optional хороша только в языке, который монады поддерживает как встроенные. На данный момент из относительно прпулярных такой язык один - хаскель. И сколько бы не пыжились радикальные сторонники ФП, в остальных языках optional выглядит как костыль, который только мешает. Вместо простого результата имеем обернутый в лишний объект. Количество проверок при этом не уменьшается, а безопасность кода не растет в сравнении с простым null.

Я бы сказал, что в джаве ошибкой был неявный null, которого как бы не видишь, а он есть. В котлине это дело поправили. Хотя вот буквально сегодня чинил NPE в котлине - но там один отчаянный человек просто заюзал not-null assertion operator более известный как "мамой клянусь тут не нулл".

Исключения выразительнее, чем Optional. Хотя по сути те же яйца.

Это далеко не те же яйца.

Совершенно разные вопросы производительности.

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

Исключения не требуют проверок почти никогда. Но усложняют покрытие кода. Утяжеляют прологи функций, хотя и не намного. Зато бросок исключения - очень дорогое дело. Это и создание объекта исключения, и динамическое определение типа в месте ловли.

Даже в питоне, где циклы и генераторы эксплуатируют исключения, и где это является пристальным предметом для оптимизации, - это всё равно дорого.

Смешивание функций, бросающих и не бросающих исключения, приводит ко внезапной просадке производительности даже в позитивных сценариях. Если внутри nothrow-функции есть что-то, что может кинуть исключение, - такая функция обязана добавить невидимый try-catch-terminate. А это значит, у неё появляется тяжёлый пролог, и её нельзя просто так взять и проинлайнить и оптимизировать.

---

И это совершенно разные системы типов.

Optional и ему подобные явно участвует в типе результата. Что вижу, о том пою.

Исключения - это теневая система типов. Если делать её явной, это приводит к изрядному синтаксическому шуму (придётся во все функции писать аннотацию throws(такое,сякое)). Причём это будет влиять на тип функции. Причём с ковариантностью.

// любители труъ-ООП, с интерфейсами это то же самое будет

void f() throws {A,B,C};
void g() thows {C,D};

void (*pfg)() throws {A,B,C,D,E} = flag ? &f : &g;
void (*pf)() = &g; // нет!

На практике это означает, что все плюнут и станут аннотировать только throws/nothrow. Или дефолтиться к throws. И мы возвращаемся к теневой системе.

Идея-то не в том чтобы уменьшить количество проверок, а чтобы уменьшить количество явных проверок. Мне несколько спокойнее, когда можно написать return *some_opt.and_then(do_this).or_else(do_that).or_else(throw_didnt_do_shit);, чем когда когда 35 миллиардов if'ов руками писать нужно.

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

Виш ю хаппи дебаг.

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

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

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

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

То, что обсуждение будет неконструктивно, я знал изначально

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

"Я знал, что на гулянке будет драка, ещё когда шёл туда с желанием подраться"

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

В профиле есть ссылка на сайт, там ссылка на гитхаб.

Я так-то не совсем уж тупой, в профиль сходил, но что-то пошло не так.

А мерит то кто будет оценивать, демократишки эти, или полу-боги полу-раки?

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

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

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

Легендарный "уровень дискуссии в Восточной Европе" :)

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

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

Конструктивно было бы привести пример с кодом, где ФП проигрывает ООП. Если таких примеров не существует, то не соглашусь с вами.

Вся кодовая база Basecamp к вашим услугам. DHH сделал рельсы именно потому, что там был чистый незамутненный маппинг объектов реального мира на объекты ООП.

Рельсы не написать в функциональной парадигме.

Может скинете небольшой кусочек, по которому можно оценить преимущества ООП? Раз уж вы это утверждаете.

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

Citation needed.

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

У ФП есть свои области раскрытия: всё, что завязано на процессы больше, чем на данные per se.

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

Я не понимаю, зачем нужно выбирать что-то одно.

Я не понимаю, зачем нужно выбирать что-то одно.

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

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

Звучит снова как ФП.

Я читал Domain Modeling Made Functional, и читал более классическую книгу Эванса. Если в первой всё было просто и понятно, хоть сейчас устраивайся в кровавый тырпрайз, то после второй я, по-моему, стал понимать даже меньше.

Звучит снова как ФП.

Ну, когда в руках молоток, всё вокруг представляется гвоздями.

Маппинг объект в реальном мире (задача) → объект в базе (строка в таблице «задачи») → объект в коде (инстанс класса «задача») → объект на странице в вебе (поля ввода и чекбоксы). И обратно. Там не нужно даже клея между слоями абстракции, всё ложится 1:1. Полная инкапсуляция. Можно добавить новый тип «объекта» вообще не трогая остальную систему. Это удобно (особенно для джунов).

Что не отменяет того, что и на ФП это будет несложно. Но плясок будет больше.

после второй я, по-моему, стал понимать даже меньше

Именно поэтому я уже очень давно не читаю книжки по специальности.

то после второй я, по-моему, стал понимать даже меньше.

Говорят, надо читать то, что писали в 80е. Оно было понятно, а потом на жирную область набежали консультанты и сделали примерно то же самое, что с позднесоветскими учебниками арифметики.

Рельсы это Ruby on Rails? Но рельсы это инструмент. А продукт, который написан на рельсах нельзя написать на ФП?

Можно. И на ассемблере можно, и в машинных кодах можно.

Главный и первый продукт - Basecamp (управление проектами). Очевидно, ООП тут больше подходит.

Пример - ядро Linux, где на сишке накостылен ООП. Идите объясните им, что они дураки и ничего не понимают в программировании.

В GTK тоже зачем-то свой ООП накостылили, вот идиёты...

Вот сейчас не понял. Что значит на си ООП написан??

Не на Си ООП написан, а код на Си написан в ООП-парадигме.

Есть паттерн макросов, метапрограмиирования если позволите.. х-Макрос называют.

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

А у них в Линуксе гцц позволяет писать замыкания через вложенные функции и лямбды, и лично у меня этот подход просто расцветает

Не для всего подряд а там где нужно, колбеки там всякие, события в петлях

Stm32 IAR
Stm32 IAR

Зы

.Методы, делегаты, инкапсуляции, виртуализации через таблицы- 98 из 99 фич ООП выросли из работы с функциями

Зыы.

Это оч просто

Void implA(arg) { impl(arr[A], arg); }

Но макросом

return Struct thing_A{

void (*method) (arg) = implA;

}

Да кто в целом то спорит, что ООП базируется на том же самом?
Если подумать, все ООП - это сахар просто.
Только про организацию и описание.
Внутри это те же самые данные в памяти и функции.
Но это не означает, что это тождественно

В GTK тоже зачем-то свой ООП накостылили, вот идиёты...

Кстати, старое мнение, что классический ООП хорошо ложится на классический GUI.

Мои примеры к вашим услугам:

const mem = pipe( distinctUntilChanged(), debounce(0), shareReplay(1) )

const ToysSource = new Rx.BehaviorSubject( [] )
const Toys = ToysSource.pipe( mem )

const FilterSource = new Rx.BehaviorSubject( toy => toy.count > 0 )
const Filter = FilterSource.pipe( mem )

const ToysFiltered = Filter.pipe(
	switchMap( filter => Toys.pipe(
		map( toys => toys.filter( filter ) )
	) ),
	mem,
)
class $my_toys {

	@mem toys(
      next = []
    ){ return next }
	
	@mem filter(
      next = toy => toy.count() > 0
    ) { return next }
	
	@mem toys_filtered() {
		return this.toys().filter( this.filter() )
	}
	
}

Это вообще что за говнокодище с какими то библиотеками, которые я почему то должен знать? Что сделать надо можно словами услышать? Если это TypeScript то вас подпускать к программированию нельзя.

По ссылке есть все подробности. Или по ссылкам ходить не церское дело?

Я там не вижу вообще никаких подробностей кроме обычного списка на мобилке, который я реализовывал 10000 раз во всех парадигмах, и то говно что ты написал я бы мало того что не пропустил никогда, так еще и уволил бы пинком.

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

Учи, студент раз два три.

А вот реализация бесконечной пагинация в моей библиотеке в ФП на TypeScript.

Я смотрю мисье синьор реакт архитектор даже классику не читал. Ну да ладно, речь не об этом.

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

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

Небольшой момент - судя по всему вы под ФП подразумеваете математический стиль из моих определений. Я трактую куда шире, и не топлю за иммутабельность, и вполне спокойно использую изменяемый стиль когда это нужно, просто без классов. И это и называю ФП. Так может и проблемы тогда нет?

Есть общепринятое определение ФП. Как вы лично что-то трактуете, никого не волнует. Впрочем, хорош придуриваться, делая вид, будто не понимаете, что делает этот тривиальный код из 3 переменных. Показывайте свой красивый и эффективный код, а не болтайте языком.

Я же сказал, я НЕ понимаю что конкретно делает этот говнокод.

Вот странное дело, новорождённая китайская нейросеть всё прекрасно поняла, а 13-илетний супер-дупер-мега-гига-убер-профи не смог.

Видишь, опять твое skill issue подтверждается.

Да возьмите любой большой проект. Я вот про webrtc могу сказать - ибо с ним работаю.

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

А теперь добавляем случаи. Есть куча разных кодеков. Т.е. вам надо в вашей функции в зависимости от кодека вызывать разные функции, которым нужен какой-то совершенно уникальный стейт, т.е. ваш стейт уже дикая структура, в которой описаны ВСЕ возможные состояния для всех кодеков. Потом, есть такая вещь, как аппаратно ускоренные энкодеры. Но они иногда ломаются внезапно по незавизящим от вас причинам, поэтому надо чтобы у вас был запасной программный энкодер, который начинает работать, если первый сломался. В ООП это очень удобно делается - у вас есть класс Encoder, и унаследованный от него SoftwareFallbackEncoder, который внутри держит 2 инстанса - аппаратный и запасной. Если замечает, что первый сломался, переключается на второй. Вся логика ограничена в одном месте и вообще больше нигде не надо про нее думать. У вас просто есть инстанс Encoder и он работет.

Хорошо, вы можете добавить статус отката в ваш мега-стейт для функции кодирования и внутри вызвать то или другое. Но есть усложнение - кодеков много (H264, VP8, VP9, ...). Для некоторых нет аппаратной реализации, для некоторых нет софтверной. Как это выглядит в ФП? В ооп - очень просто, вы в момент создания энкодера знаете, какие из двух имплементаций есть и если надо заворачиваете эту парочку в SoftwareFallbackEncoder, а если только один, то его и передаете дальше из фабричного метода.

Этого мало? Добавляем случай Simulcast. Вам надо получать сразу несколько закодированных стримов в разном разрешении. Некоторые энкодеры умеют делать это сами, а для других вам надо держать 3 копии с разными настройками. Для некоторых разрешений аппратное ускорение работает, для некоторых - нет. Обернуть все это еше и в SimulcastEncoderAdapter очень просто. Он снаружи тот же самый Encoder и всегда умеет делать что надо. Внутри он может держать 3 копии энкодеров, но что там внутри вам знать не надо. Инкапсуляция и полиморфизм действительно и помогают.

ООП дает очень удобную композицию. Вам иногда нужен энкодер, который записывает на диск битстрим для отладки? Создаете FrameDumpingEncoder, который снаружи все тот же Encoder, но внутри помимо делегации вызовов собственно энкодеру сохраняет результат в файл. Вам нужно подсчитать статистику? Добавляете класс, который считает статистику и по цепочке вызывает остаток. Весь свой стейт он хранит внутри, его не надо как-то туда через аргументы передавать.

Я очень подозреваю, что все это в ФП это будет очень мерзко.

Другой пример: алгоритмы бывает очень сложно писать в функциональном стиле эффективно. Ну это не совсем про ООП, а скорее про императивщину.

Talk is cheap, show me the code (c).

Вам тут уже полную панамку кода напихали, где ваши ответы?

Я вам конкретную ситуацию описал. Сам код вам вряд ли поможет, ибо это очень большой проект и описанная мною проблема лишь маленькая его часть и ее плохо видно за всем остальным кодом. Ну хотите кода, вот вам ссылки на исходники этих классов:
https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/media/engine/simulcast_encoder_adapter.h
https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/api/video_codecs/video_encoder_software_fallback_wrapper.h
https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/modules/video_coding/codecs/vp9/libvpx_vp9_encoder.h

Сильно упращенно (псевдокод)

class Encoder {
  public:
  virtual Bitstream Encode(VideoFrame frame) = 0;
};

class Vp9SWEncoder : public Encoder {
  public:
  Bitstream Encode(VideoFrame frame) override {
    // кодируем через libvpx
  }
};

class Vp9HWEncoder : public Encoder {
  public:
  Bitstream Encode(VideoFrame frame) override {
    // Вызываем функции ОС чтобы драйвер видеокарты что-то закодировал
  }
};

class SoftwareFallbackEncoder : public Encoder {
  public:
  Bitstream Encode(VideoFrame frame) override {
    if (!is_fallback) {
      Bitstream encoded = hw_encoder_->Encode(frame);
      if (!encoded.IsError()) return encoded;
      is_fallback_ = true;
    }
    return sw_encoder_->Encode();
  }
  
  private:
  bool is_fallback_ = false;

  Encoder * sw_encoder_;
  Encoder * hw_encoder_;
};

Теперь представьте, что есть еще VP8SWEncoder (но не HWEncoder!), а также H265HWEncoder (но не SWEncoder!). И вы где-то во время исполения узнаете, каким кодеком вам надо кодировать.

И в ООП вы или создаете VP8SWEncoder или H265HWEncoder или SoftwareFallbackEncoder(VP9SWEncoder, VP9HWEncoder). А потом просто вызвыаете Encode() у инстанса не думая о том, какой зоопарк кодеков у вас на самом деле.

А что вы делаете в ФП? Тоже тогда с кодом, пожалуйста.

Это как раз довольно спорный пример, потому что полиморфизм не обязательно реализовывать через классы, он прекрасно реализуется в классическом ФП.

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE TypeFamilies #-}

class Codec a where
    type Out a :: * 
    encode :: a -> Out a

instance Codec Encoder1 where  
    type Out Encoder1 = Encoded
    encode = ...

instance Codec Encoder2 where 
    type Out Encoder2 = Encoded 
    encode = ...

полиморфизм не обязательно реализовывать через классы

В коде ключевые слова class, instance... Ну допустим это другое. Я не против что это возможно сделать на ФП. Но это будет очень сложно и неудобно. Так-то и fizzbuzz можно на ООП нафигачить, как в той известной шутке, но лучше ли это процедурного стиля?

Где у вас вот в этом коде будут вызовы для кодирования данным конкретным кодеком, где будет состояние is_fallback_? А где состояние каждого отдельного кодека? Можете его допилить хотя бы до того же уровня абстракции что и у меня, с функциональность fallbackEncoder? А то получается, что можно реализовать пристейший тривиальнейший пример и даже не так страшно выглядит. А вот уже в реальном проекте, где классов больше двух и всякие сложности бывают - получается очень неудобно и неподдерживаемо.

В коде ключевые слова class, instance... Ну допустим это другое.

Это действительно другое.

class — это обобщение ООПных интерфейсов или плюсовых концептов. Концепт «итератор» был бы классом. «Моноид» на самом деле является классом. «Любая монада, поддерживающая возможность хранить стейт типа Foo», является классом.

Инстанс — это объявление, что данный тип соответствует данному интерфейсу/концепту, и реализация требуемых интерфейсом/концептов методов/типов.

Ну давайте упрастим и возьмем ваш тривиальный пример.

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

type Encoding = "xxx" | "yyy"

const encodeXxx(frame: VideoFrame): Bitstream {
   ...
}

const encodeYyy(frame: VideoFrame): Bitstream {
   ...
}

const encoders: Record<Encoding, (frame: VideoFrame) => Bitstream> = {
  xxx: encodeXxx,
  yyy: encodeYyy,
}

// Использование:

const main = () => {
  const frame = getFrame()
  const stream = encoders[data.encoding](frame)
}

А где там fallback который для одних кодеков есть, а для других нет? Где вот хотя бы is_fallback стейт хранится? Потом, учтите, что у каждого экземпляра энкодера свой собственный стейт. У вас в системе не один единственный энкодер и у каждого должен быть свой is_fallback_.

const encoders = {
  xxx: {
    encode: encodeXxx,
    fallback: encodeYyy,
  }
  yyy: {
    encode: encodeXxx,
    fallback: encodeZzz,
  }
} as const

// Использование

const main = () => {
  let useFallback = false

  while (...) {
    const frame = getFrame()
    
    const {encode, fallback} = encoders[frame.encoding]

    const stream = useFallback
      ? fallback(frame)
      : (() => {
          try {
            return encode(frame)
          } catch {
            fallback = true
            return fallback(frame)
          } 
        })()
    
    // Используем stream
  }
}

Ваш код использования не приведен, додумываю сам.

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

В итоге вы придете к тому же, что и другие комментаторы (как @IUIUIUIUIUIUIUI ниже) - у вас будет этот is_fallback внутри замыкания, которое будет возвращать новое замыкание с новым is_fallback, если вы захотите сохранить чистость функций. Для реализации кучи вариантов у вас будет какое-то наследование с ключевыми словами class и instance, полиморфный код работающий с базовым энкодером, который на самом деле какой-то наследуемый от него тип, и фабричная функция, возвращающая нужное замыкание в зависимости от настроек кодека.

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

Я похож на ChatGPT который должен дописывать код, каждый раз получая все новые комментарии?

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

К моему коду выше добавляем:

class Encoder {
  public:
  virtual Bitstream Encode(VideoFrame frame) = 0;
  virtual void RequestKeyFrame() = 0;
};

// внутри класса
SoftwareFallbackEncoder::RequestKeyFrame() {
  if (is_fallback_) sw_encoder_->RequestKeyFrame();
  else hw_encoder_->RequestKeyFrame();
}

Encoder* CreateEncoder(CodecType codec) {
  switch (codec) {
      case vp8: return SoftwareFallbackWrapper(
        new Vp8SWEncoder(), new VP8HWEncoder())
      case vp9: return new VP9SWEncoder();
      case h264: return new H264HWEncoder();
  }
}


int main() {
 ...
 codecs = GetCodecs(); 
  // может быть любой набор вроде {vp8, vp8, h264} или {vp9}, 
  // или даже {h264, h264, h264, vp8, vp8, vp8, vp8}
 for (auto codec : codecs) {
   encoders.push_back(CreateEncoder(codec))
 }

  while (auto event = GetEvent()) {
    switch (event.Type()) {
        case kGotFrame: {
          for (auto encoder : encoders) {
            encoder->Encode(event.GetFrame());
          }
        }
        break;
        case kRequestedKeyFrame: {
          for (auto encoder : encoders) {
            encoder->RequestKeyFrame();
          }
        }
        break;
    }
  }
  
}

Методы Encode и RequestKeyFrame у XXX(H|S)Encoder изменяют какой-то уникальный для каждого кодека внутренний стейт.

Код
type XxxState = {
  fallback: boolean
  fallbackState: YyyState
}
type YyyState = {}

const encodeXxx = (state: XxxState, frame: Frame) => {}
const encodeYyy = (state: YyyState, frame: Frame) => {}
const requestFrameYyy = (state: YyyState) => {}
const requestFrameXxx = (state: XxxState) => {
  if (state.fallback) {
    return requestFrameYyy(state.fallbackState)
  }
  // ...
}

const main = () => {
  const codecs = getCodecs()

  const encoders = codecs.map((encoding) => {
    switch (encoding) {
      case 'xxx': {
        const state: XxxState = {fallback: false, fallbackState: {}}
        return {
          encode: (frame: Frame) => encodeXxx(state, frame),
          requestFrame: () => requestFrameXxx(state),
        }
      }
      case 'yyy': {
        const state: YyyState = {}
        return {
          encode: (frame: Frame) => encodeYyy(state, frame),
          requestFrame: () => requestFrameYyy(state),
        }
      }
    }
  })

  while (true) {
    const event = getEvent()
    if (!event) {break}

    for (const {encode, requestFrame} of encoders) {
      switch (event.type) {
        case 'got-frame': {
          encode(event.frame)
          break
        }
        case 'requested-key-frame': {
          requestFrame()
          break
        }
      }
    }
  }
}

Здесь полный код, в отличие от вашего. Изменяемое состояние, разное у разных кодеков. У одного есть fallback.

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

Во-первых, у вас не то же самое, у вас тут всего 2 кодека и один из них то идет отдельно, то идет с fallback ко второму. Сделайте 4. XXXhw, XXXsw, YYYsw, ZZZhw.

Во-вторых, ваш код вообще неподдерживаем, в отличии от моего и других комметаторов, которым пришлось эмулировать для этого ООП. Что вы будете делать, если у вас добавится еще один кодек, который уже пойдет парочкой к одному из кодеков без fallback? Станете копировать логику fallback во все методы нового encoderWWW? Да, в тривиальном и упрощенном примере у вас копируется не так много кода. Но я вам ссылки в самом начале на настоящий код дал, там его много, гораздо больше, чем один if(fallback)... . И там несколько кодеков идут c fallback. Зря я, конечно, упрощенный пример вам написал. Думал всем очевидно, что надо писать код поддерживаемым, но вам эта концепция не близка.

Потом, если надо добавить еще метод, кроме двух encode, requestKeyFrame - вы в каждом кодеке будете копировать fallback логику? А если логика fallback поменяется, вы ее во всех 10 местах будете изменять, надеясь, что вы нигде не ошиблись?

А если, что я раньше в тексте указывал, надо добавить еще один уровень кроме softwarefallback с какой-то своей логикой? Например, dumpingEncoder, который еще и в файл пишет результат. И оно навешивается на любой из кодеков по внешнему условию?

Но даже со всеми этими проблемами вы все-равно почти используете ООП. у вас там набор функций вместе со стейтами лежат в const encoders. Это фактически объект. Когда вам надоест код копипастить вы начнете использовать полиморфизм и наследование и у вас уже будет ООП.

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

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

ваш код вообще неподдерживаем  ... что вы будете делать, если

Я уже от вас много раз слышал "а что будет если", и без проблем реализовывал ваши требования. Как я говорил это был последний раз.

Зря я, конечно, упрощенный пример вам написал

За несколько попыток не смогли придумать ничего сложнореализуемого на ФП, и дальше не придумаете.

вы все-равно почти используете ООП

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

Принципиальная разница - классы, которые:

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

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

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

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

Многие минусы современного ООП на классах можете еще раз перечитать в статье.

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

При желании их можно переиспользовать в любом другом типе в отличие от методов класса.

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

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

Есть куча ООП языков, где вместо наследования, как в C++ используется именно компановка (го, например). Потом, даже на C++ можно делать компановку, если наследоваться только от абстарктных классов и получать нужные объекты к конструкторе (ровно как и вы компонуете). Именно так, например, FallbackEncoder и сделан у меня, он же не получает все методы и данные двух переданных ему encoder'ов?

никаких классов, наследования и прочих прелестей ООП языков

Тут же куча комменататоров использовали наследование замыканий через class/instance, потому что это удобнее. Что в общем-то практически трейты из rust - общепризнанного ООП языка.

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

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

а в их общем предке

А если у них уже есть предки? Статью перечитайте.

Есть куча ООП языков .. го, например

Go как раз язык ФП.

Go как раз язык ФП.

Изучите для начала хоть один ФП-язык, например Elixir или Haskell, тогда поймёте, что Go вовсе не функциональный. И TS, разумеется, тоже.

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

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

Go как раз язык ФП.

ФП — это набор техник, которые не реализуются на 100% ни в одном языке, но функциональные языки в основном их покрывают:

  1. Функции — first class values

  2. Развитые строгие статические системы типов

  3. Неизменяемые структуры данных

  4. Контроль эффектов

  5. «Декларативный» стиль

И, наверное, я что-то забыл.

Go, конечно, поддерживает часть этого, но основной предпочитаемый стиль — императивный.

Всё-таки основная определяющая характеристика ФП -- программы в нём представляются в виде композиции функций (в математическом смысле, отображений входов на выходы). Это противопоставляет его императивному программированию, где программа представляется в виде последовательности инструкций (присваиваний, условных переходов, вызова подпрограмм/процедур и т. д.)

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

и без проблем реализовывал ваши требования

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

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

без огромного синтаксиса

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

без проблемы банана и макаки

В обоих примерах на C++ нет проблемы банана и макаки, потому что там код полностью аналогичен вашему. Значит так и нужно делать?

и других проблем из статьи

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

А кроме того к ним добавляются проблемы копипасты и ее дальнейшей поддержки. Примеры в вашем коде я тоже приводил. Для решения этих проблем и было придумано ООП.

у вас в обоих примерах (кодеки и тулы) "синтаксис" более огромный

Речь про синтаксис языков из проблем ООП из статьи. И он в обоих примерах простейший.

В обоих примерах на C++ нет проблемы банана и макаки

Это потому что примеры погонялись именно так. Что если нужно написать кодек, фолбеки которого используют единое состояние? А если нужно, чтоб одна функция кодека работала как у кодека х, а другая как у кодека y?

При этом сделали такой же конструктор createState ... добавляются проблемы копипасты

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

- и без проблем реализовывал ваши требования
- Вы сделали ровно то же самое, что делает компилятор для ООП
- Если я сделал все то же самое
- Речь про синтаксис языков из проблем ООП из статьи

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

Это потому что примеры погонялись именно так.

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

Что если нужно написать кодек, фолбеки которого используют единое состояние?
А если нужно, чтоб одна функция кодека работала как у кодека х, а другая как у кодека y?

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

Вы кажется вообще путаете примеры кода, здесь речь о коде с кодеками.

Я про оба примера говорю, потому что они схожие. С кодеками у вас нет createState потому что у вас state пустой type YyyState = {}, и потому что вы выбор кодеков сделали не так, как было в исходном примере.

тк копипасты практически нет

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

В целом копипаста у вас встречается в 18 строках из 116, а коде на C++ только в 2, в которых ее все равно убрать нельзя. То есть у вас 16/116 = 14% копипасты, а на C++ 0%.

А еще у вас там важное отличие, у вас весь state - const. Но приведенный мной код меняет is_fallback_, если произойдет ошибка. Вы этот код опустили. Оно вообще будет работать в вашем подходе?

const только для ссылки на state, сам state мутабельный, и соответственно fallback.

Здесь полный код
Лаконично

Ага, как же. У вас код не эквивалентный - установки is_fallback нет, выбора requestFrameXxx / requestFrameYyy тоже. Они относятся к разным кодекам и могут работать независимо, должна быть третья функция, которая выбирает одну из них, а не одна вызываться из другой.
Там для fallback-выбора можно передать любые 2 энкодера, а у вас жестко задан выбор только xxx/yyy. Для другой пары копипастить будете? Как у вас сделать вот такую конструкцию?

case vp9f: return new SoftwareFallbackEncoder(
  new Vp9SWEncoder(), new VP9HWEncoder()
);
case vp8f: return new SoftwareFallbackEncoder(
  new Vp8SWEncoder(), new VP8HWEncoder()
);
case vp9s: return new Vp9SWEncoder();
case vp8s: return new Vp8SWEncoder();
case vp9h: return new Vp9HWEncoder();
case vp8h: return new Vp8HWEncoder();

Не говоря уже о том, что вы реализовали ООП вручную. Конструкции вида encode: (frame: Frame) => encodeXxx(state, frame) это и есть как работает таблица виртуальных методов в ООП.

Просили код полностью, тогда и сами пишите полностью, без сокращений и "xxx". Что за увиливания?) Тогда и будем сравнивать, насколько у вас лаконично. Если этот пример полностью написать в вашем стиле, там строк будет еще больше, чем на C++, и много копипасты.

а теперь представье что у вас мега класс с кодеками будет активным(если хотите чуточку реактивным) и вы загрузите весь класс, отсюда следует надо запускать арбитра, который точно поймёт какой нужен кодек и вызовет только его, и кодеки искать не надо максимум хэш мапу можно завести по имени

(str,*codec) обьекты хранить накладно лучше хранить адреса тоесть чтото ближе к указателям(умным), я знаю что это спорно но обьекты хранить накладно, файл хранить 1 ок, но только 1 и давать на него указатель вот, а так обьекты не то я думаю

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

Блин, вся соль в нюансах, в том, как написать минимально геморройно. Так концепции — статическое связывание vs динамическое связывание, известны со времён OS/360, если не раньше.

Пример — хаскельные классы типов похожи на виртуальное наследование в С++, только вызовы перегруженных функций в Хаскеле как правило девиртуализованы, а в С++ — как правило не девиртуализованы. В результате, у вас в хаскеле в 99.99% случаев работает inline, а в С++ — нет.

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

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

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

Такую VMT даже на ассемблере легко написать.

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

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

Достаточно. Но удобнее ли? Не создает ли это дополнительных проблем?

ООП даст вам, по сути, только одно

Суть классов - данные хранятся вместе с методами их обработки. И это часто очень удобно.

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

Нет, стейт будет обычная структура в которой сохранены другие структуры. Как у вас

  bool is_fallback_ = false;

  Encoder * sw_encoder_;
  Encoder * hw_encoder_;

так и в ФП:

data SoftwareFallbackEncoder sw hw = SoftwareFallbackEncoder {
  _sw_fallback_encoder :: sw,
  _hw_encoder :: hw,
  _is_fallback :: Bool
}

ООП дает очень удобную композицию. Вам иногда нужен энкодер, который записывает на диск битстрим для отладки? Создаете FrameDumpingEncoder, который снаружи все тот же Encoder, но внутри помимо делегации вызовов собственно энкодеру сохраняет результат в файл. Вам нужно подсчитать статистику? Добавляете класс, который считает статистику и по цепочке вызывает остаток. Весь свой стейт он хранит внутри, его не надо как-то туда через аргументы передавать.

Что мешает то же самое сделать в ФП?

data DumpingEncoder e = DumpingEncoder { _inner :: e, _dump :: [VideoFrame] }
makeLenses ''DumpingEncoder

instance Encoder e => Encoder (DumpingEncoder e) where
  encode frame = do
    modifying dump (frame:)
    zoom inner $ encode frame 

Другой пример: алгоритмы бывает очень сложно писать в функциональном стиле эффективно. Ну это не совсем про ООП, а скорее про императивщину.

Это, увы, часто так, но тут на помощь приходит CAF, ST и разные оптимизации внутри GHC

А что вы делаете в ФП? Тоже тогда с кодом, пожалуйста.


{-# LANGUAGE TemplateHaskell #-}
module Main (main) where

import Control.Monad.State
import Control.Lens

data Bitstream
data VideoFrame

class Encoder a where
  encode :: VideoFrame -> State a (Maybe Bitstream)

data Vp9SWEncoder
data Vp9HWEncoder

instance Encoder Vp9SWEncoder where
  encode = undefined

instance Encoder Vp9HWEncoder where
  encode = undefined


data SoftwareFallbackEncoder sw hw = SoftwareFallbackEncoder {
  _sw_fallback_encoder :: sw,
  _hw_encoder :: hw,
  _is_fallback :: Bool
}
makeLenses ''SoftwareFallbackEncoder


instance (Encoder sw, Encoder hw) => Encoder (SoftwareFallbackEncoder sw hw) where
  encode frame = do
    use_fallback <- gets $ view is_fallback

    if use_fallback 
      then do
        maybe_encoded <- zoom hw_encoder $ encode frame
        
        case maybe_encoded of
          Just encoded -> return $ Just encoded 
          Nothing -> do
            is_fallback .= True
            zoom sw_fallback_encoder $ encode frame
          
      else
        zoom sw_fallback_encoder $ encode frame 

Можно было ещё ContT затянуть, чтобы не дублировать zoom sw_fallback_encoder $ encode frame, но показалось, что тут будет больше вреда чем пользы

Что мешает то же самое сделать в ФП?

А приведите там код, как вы в рантайме, в зависимости от пользовательского ввода решаете делать ли просто Encoder или заворавичвать его в DumpingEncoder? Как вы потом его вызываете?

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

А приведите там код, как вы в рантайме, в зависимости от пользовательского ввода решаете делать ли просто Encoder или заворавичвать его в DumpingEncoder? Как вы потом его вызываете?

-- Поправил типы, чтобы их можно было сконструировать и выводить на экран
data Bitstream = Bitstream deriving Show
data VideoFrame = VideoFrame Int deriving Show

data Vp9SWEncoder = Vp9SWEncoder deriving Show
data Vp9HWEncoder = Vp9HWEncoder deriving Show

-- Поправил инсансы, чтобы не выбрасывали ошибки
instance Encoder Vp9SWEncoder where
  encode _ = return Nothing

instance Encoder Vp9HWEncoder where
  encode _ = return Nothing

-- Полиморфный код
do_something_with_encoder :: Encoder a => State a ()
do_something_with_encoder = do
  encode $ VideoFrame 0
  encode $ VideoFrame 1
  encode $ VideoFrame 2

  return ()

-- Функция вызывающая полиморфный код
main :: IO ()
main = do
  user_input <- getLine
  if user_input == "wrap" 
    then do
      let encoder = DumpingEncoder Vp9SWEncoder []
      let (DumpingEncoder _ dump) = execState do_something_with_encoder encoder 

      print $ reverse dump 
    else do
      let _ = execState do_something_with_encoder Vp9SWEncoder 
      putStrLn "No dump"

  return ()

Потом, у вас тут не ООП разве? Классы есть, экземпляры какие-то есть, данные, насколько я понимаю, лежат рядом с функциями где-то.

Это классы типов. Из мейнстрима больше всего похожи на trait-ы в rust, но нет, это не ООП.

Где лежит is_fallback, ведь он же меняется?

Строчка

  _is_fallback :: Bool

Внутри SoftwareFallbackEncoder

Функции нифига не чистые, или я что-то напутал?

Или это какой-то стейт неявно передаваемый функции, точно так же как методам класса неявно передается инстанс объекта

Во-первых мутабельность не всегда противоречит чистоте (см. монаду ST). Во-вторых, тут мутабельности действительно нет. State конструирует сложную, но всё же функцию без мутабельности.
Вы, судя по всему, на C++ пишете. Там это аналогично тому, чтобы вместо

  x = 10;
  rest

Писать

  [](int x) { rest } (10)

В чем тогда вообще разница с ООП?

Все эффекты (в данном случае доступ к состоянию) явно выражены в типах

-- Функция вызывающая полиморфный код

А если надо разделять создание и использование? Вот вы сейчас создаете энкодер, но видео фрейма у вас под рукой нет, вызывать кодирование вы будете потом, в другой часте кода? Или так нельзя и вам придется в другую часто кода вот этот user input тащить?

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

Внутри SoftwareFallbackEncoder

А где хранится SoftwareFallbackEncoder? Я вот чего не понимаю, у вас в зависимости от пришедших данных, ну или ввода, как в примере, состояние может быть просто vp9sw, может быть SoftwareFallbackEncoder, оборачивающий vp9sw и vp9hw, а может быть frameDumping (который какой-нибудь файл открытый хранит) и при этом оборачивающий или просто vp9sw или опять же FallbackWrapper. Комбинаторный взрыв вариантов типов. Вам надо это состояние для каждого видео потока отдельно где-то держать и в нужную функцию передавать. Как?

Все эффекты (в данном случае доступ к состоянию) явно выражены в типах

Я правильно понимаю, что если у вас там у encoder будет кроме возможности "закодировать фрейм" еще и функциональность "переконфигурировать себя под измененный битрейт", которая вызвыается отдельно от кодировки фрейма, потому что пораждается событями от сети, а не от камеры, то в этом типе тоже будет выражено, что там есть доступ к тому же внутреннему состоянию энкодера?

Это в ООП эквивалентно "вызовы методов объекта имеют доступ к состоянию этого объекта". Очевидная вещь. Возможно, вы в ФП можете чуть более тонко это указывать вроде как какая часть состояния может поменятся, а какая нет. Это просто не так развито в ООП, но в том же с++ можно пометить функцию const, указав в ее сигнатуре, что она стейт не меняет. Теоретически так же можно было бы указывать и про разные части стейта более тонко указывая эффекты доступа к состоянию. Но это не делают, потому что это создает сложности. Инкапсуляция сильно снижает общую сложность, лучше пусть пользователи не знают, что конкретно там внутри и как оно меняется, пусть верят интерфейсу и не ломают голову.

А если надо разделять создание и использование? Вот вы сейчас создаете энкодер, но видео фрейма у вас под рукой нет, вызывать кодирование вы будете потом, в другой часте кода? Или так нельзя и вам придется в другую часто кода вот этот user input тащить?

Разделить можно, но тогда придётся энкодеры не чистыми делать (моя версия энкодера собирала данные, но никуда их не записывала), потому что из AnyEncoder нельзя сделать downcast к DumpingEncoder, чтобы забрать дампы. Выглядеть будет как-то так:

data AnyEncoder = forall a. Encoder a => AnyEncoder a

createEncoder :: IO AnyEncoder
createEncoder = do
  user_input <- getLine
  return $ if user_input == "wrap" 
    then AnyEncoder $ DumpingEncoder Vp9SWEncoder []
    else AnyEncoder Vp9SWEncoder

main :: IO ()
main = do
  (AnyEncoder e) <- createEncoder 
  let _ = execState do_something_with_encoder e

  return ()

А где хранится SoftwareFallbackEncoder?

Он в привычном понимании нигде не хранится. State s a в следует читать как функцию типа s -> (a, s) плюс синтаксический сахар. Сам тип SoftwareFallbackEncoder возникает в этих строчках:

class Encoder a where
  encode :: VideoFrame -> State a (Maybe Bitstream)

instance (Encoder sw, Encoder hw) => Encoder (SoftwareFallbackEncoder sw hw) where
  encode frame = do

Когда я определяю инстанс энкодера в типе encode вместо a подставляется SoftwareFallbackEncoder sw hw, получается State (SoftwareFallbackEncoder sw hw) (Maybe Bitstream). А внутри encode можно привычным образом обращаться к состоянию.

Комбинаторный взрыв вариантов типов. Вам надо это состояние для каждого видео потока отдельно где-то держать и в нужную функцию передавать. Как?

Его не нужно отдельно держать или передавать вручную. Оно спрятано внутри структуры SoftwareFallbackEncoder. Если хочется чтобы и типы были спрятаны, то их можно завернуть с помощью forall, как в AnyEncoder чуть выше.

Я правильно понимаю, что если у вас там у encoder будет кроме возможности "закодировать фрейм" еще и функциональность "переконфигурировать себя под измененный битрейт", которая вызвыается отдельно от кодировки фрейма, потому что пораждается событями от сети, а не от камеры, то в этом типе тоже будет выражено, что там есть доступ к тому же внутреннему состоянию энкодера?

Мой энкодер пока что является чистым. Он не умеет взаимодействовать с другими потоками, ему его тип запрещает это делать. В теории функции которые пользуются энкодером могут хранить его состояние в IORef или MVar, тогда получится переконфигурировать из какого-то другого места, но если хочется более тонкой многопоточности (без мьютекса на всю структуру), то придётся менять сам тип.

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

ФП на инкапсуляцию с другой стороны смотрит. В ООП, если мне придёт VideoEncoder я ничего не смогу про него сказать. Безопасно ли к нему обращаться из разных потоков? Ходит ли он в сеть? Не испортит ли состояния других объектов? Пользователь не может полноценно абстрагироваться и смотреть только на интерфейс, поскольку интерфейс ничего не гарантирует. Сравните с ФП:

class Encoder a where
  encode :: VideoFrame -> State a (Maybe Bitstream)

Просто посмотрев на типы я знаю, что функция не ходит в сеть, не меняет чужие состояния и её безопасно запускать многопоточно. Или вот ещё из моего пет-проекта:

parser :: Parser Bencode
runParser :: MonadFail m => Parser t -> B.ByteString -> m t
mkTorrent :: Bencode -> Maybe Torrent

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

ФП на инкапсуляцию с другой стороны смотрит.

Согласен, что ФП тут дает больше описательной силы для интерфейса. Не согласен, что разница принципиальна. Почему, расписал в ветке ниже: https://habr.com/ru/articles/885980/comments/#comment_27977022

Соседние ораторы показали много разных сложных примеров, а я покажу рабоче-колхозно:

type EncodeResult = Either Error BitStream
type Encoder = VideoFrame → IO EncodeResult

-- создаёт энкодер, загружая libvpx и потенциально фейлясь
vp9swEncoder :: IO (Maybe Encoder)
vp9swEncoder = undefined

-- ну дрова уж точно могут зафейлиться при инициализации
vp9hwEncoder :: IO (Maybe Encoder)
vp9hwEncoder = undefined

data FallbackState = CanUseHW | ShouldFallback

swFallbackEncoder :: Encoder → Encoder → IO Encoder
swFallbackEncoder hwEncoder swEncoder = do
  isFallback ← newIORef CanUseHW
  pure $ \frame → readIORef isFallback >>= \case
    ShouldFallback → swEncoder frame
    CanUseHW → hwEncoder frame >>= \case
      Right bs → pure $ Right bs
      Left _ → writeIORef isFallback ShouldFallback >> swEncoder frame

Дальше это можно обмазать типами, чтобы swFallbackEncoder можно было создать только из пары SW и HW-энкодеров, но это предлагается читателю в качестве упражнения.

И в ООП вы или создаете VP8SWEncoder или H265HWEncoder или SoftwareFallbackEncoder(VP9SWEncoder, VP9HWEncoder). А потом просто вызвыаете Encode() у инстанса не думая о том, какой зоопарк кодеков у вас на самом деле.

А в чём вопрос? Просто возвращаете функцию.

mkEncoder :: String → IO (Maybe Encoder)
mkEncoder "vp9" = do
  vp9swResult ← vp9swEncoder
  vp9hwResult ← vp9hwEncoder
  case (vp9hwResult, vp9swResult) of
    (Just vp9hw, Just vp9sw) → pure <$> swFallbackEncoder vp9hw vp9sw
    (_, _) → pure $ msum [vp9hwResult, vp9swResult]
mkEncoder "vp9:force-sw" = vp9swEncoder
mkEncoder "vp8" = vp8swEncoder
mkEncoder "h265" = h265hwEncoder
mkEncoder _ = pure Nothing

Блин, извините, я идиот, зачем-то флаги держу, когда можно без флагов, и просто в стейте хранить саму функцию, которую надо вызывать. Так гораздо чище, и в кои-то веки пригодился Control.Monad.mfix:

swFallbackEncoder :: Encoder → Encoder → IO Encoder
swFallbackEncoder hwEncoder swEncoder = do
  runner ← mfix $ \runner → newIORef $ \frame → hwEncoder frame >>= \case
    Right bs → pure $ Right bs
    Left err → writeIORef runner swEncoder >> swEncoder frame
  pure $ \frame → readIORef runner >>= ($ frame)

Спасибо, но я все еще не понимаю, а где хранится FallbackState для каждого энкодера в системе? Вот эта mkEncoder она могла вернуть fallback врапер, а могла просто энкодер. Как вы потом ее результат вызваете?

Спасибо, но я все еще не понимаю, а где хранится FallbackState для каждого энкодера в системе?

Он хранится для каждого созданного «экземпляра» fallback-энкодера в замыкании соответствующей функции. Каждый раз, когда я пишу

encoder ← swFallbackEncoder vp9hw vp9sw

то у меня «запускается» тело swFallbackEncoder, которое создаёт (newIORef) мутабельную переменную (isFallback), где лежит FallbackState (CanUseHW по умолчанию), и возвращает лямбду, которая на каждый фрейм проверяет эту захваченную переменную.

На плюсах это выглядело бы примерно как

auto swFallbackEncoder(auto hw, auto sw)
{
  auto shouldFallback = std::make_shared<bool>(false);
  return [=](VideoFrame frame)
  {
    if (*shouldFallback)
      return sw(frame);
    auto res = hw(frame);
    if (!res)
    {
      res = sw(frame)
      *shouldFallback = true;
    }
    return res;
  };
}

Вот эта mkEncoder она могла вернуть fallback врапер, а могла просто энкодер. Как вы потом ее результат вызваете?

Как функцию :]

runWithConfig config = do
  maybeEnc ← mkEncoder (encoderName config)
  case maybeEnc of
    Nothing → putStrLn "uh oh"
    Just enc → enc frame -- вызываю!

Можно написать тестовый код вроде

vp9swEncoder :: IO (Maybe Encoder)
vp9swEncoder = pure $ pure $ \frame → putStrLn "doing sw..." >> pure (Right $ show frame)

vp9hwEncoder :: IO (Maybe Encoder)
vp9hwEncoder = pure $ pure $ \frame → putStrLn "trying hw…" >> pure (Left "unable to encode")

и потом

main :: IO ()
main = do
  -- лень проверять just/nothing, я знаю, что там just
  Just enc1 ← mkEncoder "vp9"
  Just enc2 ← mkEncoder "vp9"
  enc1 10 >>= print
  enc1 11 >>= print
  enc2 20 >>= print
  enc2 21 >>= print

выведет

trying hw…
doing sw...
Right "10"
doing sw...
Right "11"
trying hw…
doing sw...
Right "20"
doing sw...
Right "21"

обратите внимание — trying hw для каждого отдельного энкодера печатается только раз.

Он хранится для каждого созданного «экземпляра» fallback-энкодера в замыкании соответствующей функции.

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

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

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

Есть такой же полиморфизм, что-то типа наследования (классы и инстансы)

На всякий случай, ещё раз подчеркну, что классы — это объявления контрактов/концептов, а инстансы — сообщение компилятору, что данный тип (или множество типов) удовлетворяет контракту, и описание, как именно они это делают. С ООП тут есть некоторое пересечение, но очень отдалённое.

Например, может быть класс и инстансы

class Mult left right result | left, right → result where
  multiply :: left → right → result

instance Mult (Matrix m n) (Matrix n k) (Matrix m k) where
  multiply = ...

instance Mult (Vect n) (Vect n) (Vect n) where
  multiply = ...

где в первой строке Mult left right result означает, что «контракт» описывает отношения трёх типов, а | left, right → result означает, что первые два типа автоматически определяют третий единственным образом (и компилятор это проверяет для инстансов и пользуется при тайпчекинге/выводе типов).

Я затрудняюсь выразить ровно это на ООП с такой же степенью симметрии.

Или можно (но не нужно) на тайпклассах делать арифметику Пеано, и чтобы компилятор потом проверял, что соответствующие равенства выполняются.

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

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

Минусы, я думаю, тоже есть.

Зависит от задач, конечно, но я ни разу не ловил себя на желании «эх вот бы щас тут ООП-классик бы навернуть».

Надо будет функции передавать параметр конкретной операции? Или есть более простой механизм?

Зависит от более общего контекста. Можно так, да:

data Input
  = EncodeFrame VideoFrame
  | NotifyMissingInput
  | ReconfigureBitrate Bitrate

type Encoder = Input → IO (Either Error BitStream)

Можно

data Encoder m = Encoder
  { encodeFrame :: VideoFrame → m (Either Error BitStream)
  , notifyMissing :: m ()
  , reconfigureBitrate :: Bitrate → m ()
  }

— то есть, тупо рекорд (структура) с разными функциями, которая может создаваться через

vp8encoder :: Params → IO (Encoder IO)
vp8encoder params = do
  commonRef ← newIORef ...
  let encodeFrame = ...
      notifyMissing = ...
      reconfigureBitrate = ...
  pure $ Encoder {..}

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

Это выглядит совсем как ООП, только, опять же, так как это полноценные first-class-citizen-данные, можно легко их собирать из отдельных кусочков и легко модифицировать. Паттерны и языковые фичи вроде миксинов просто становятся не нужны.

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

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

Проблема ООП не в том, что вам не видно, есть там сайд-эффекты или нет. Если не использовать антипаттерн "миллионы глобальных флагов", то любой не-const метод меняет состояние класса. Проблема в том, что вы не видите как именно это состояние меняется. Никак нельзя описать ограничения вроде "после вызова MethodA с такими-то параметрами, перед вызовом MethodC с такими-то параметрами, надо обязательно вызвать MethodB с такими-то параметрами". Так и в ФП, вы явно видите, что вот эта функция возвращает новый стейт, она его меняет, но как именно - не видно. В ФП, теоретически, вы могли бы описать это через возвращаемые типы, но на практике это невозможно, ибо комбинаторный взрыв и расписывать все внутренние состояния одного только энкодера вы задолбаетесь. И как это в полиморфном коде работает, я вообще не представляю.

Какие там еще основные приемущества ФП? Авто-распараллеливание? Не работает, потому что у вас почти все функции принимают результат работы предыдущих функций. И если вы хотите разные методы одного Encoder параллельно выполнять, то у вас те же самые проблемы, что и в императивных языках.

Это не значит, что ФП хуже ООП, не поймите меня не правильно. Это значит, что хорошо использовать инструменты подходящие под задачу и хороший язык программирования должен давать возможности многих подходов. А еще, приведенная мной задача все еще конр-пример к статье автора, раз вам надо использовать что-то, что крякает, скорее как ООП, а не как ФП, для ее решения.

Мне кажется, что тут вы фактически эмулируете ООП, теряя все основные приемущества ФП, потому что этот подход дает гибкость и абстрагирование необходимые в любом достаточно сложном коде, ради которых это самое ООП и придумали.

Тут есть два аспекта.

Во-первых, сама задача не очень богата на бизнес-логику, тестируемую в изоляции от внешнего мира. Условно, писать шелл-скрипты на ФП тоже не то чтобы демонстрирует все его прелести, а тут всё сводится к дёрганью внешних API.

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

Проблема ООП не в том, что вам не видно, есть там сайд-эффекты или нет. Если не использовать антипаттерн "миллионы глобальных флагов", то любой не-const метод меняет состояние класса.

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

Никак нельзя описать ограничения вроде "после вызова MethodA с такими-то параметрами, перед вызовом MethodC с такими-то параметрами, надо обязательно вызвать MethodB с такими-то параметрами". Так и в ФП, вы явно видите, что вот эта функция возвращает новый стейт, она его меняет, но как именно - не видно. В ФП, теоретически, вы могли бы описать это через возвращаемые типы, но на практике это невозможно, ибо комбинаторный взрыв и расписывать все внутренние состояния одного только энкодера вы задолбаетесь.

Почему?

Я недостаточно знаком с энкодерами, поэтому извиняюсь за возможно глупую конкретику, но в общем виде никто не мешает делать (IO на возвращаемые типы навесьте по вкусу, сути это особо не меняет):

data EncoderState
  = Initialized
  | Ready
  | KeyFrameExpected
  | AnyFrameExpected
  | LostStream

data Encoder s = Encoder { encLibHandle :: Handle }

load :: String → Either LoadError (Encoder Initialized)

configure :: Config → Encoder Initialized → Either ConfigError (Encoder Ready)

class CanFeedKeyFrameAt st
instance CanFeedKeyFrameAt Ready
instance CanFeedKeyFrameAt KeyFrameExpected
instance CanFeedKeyFrameAt AnyFrameExpected

feedKeyframe :: CanFeedKeyFrameAt st => Keyframe → Encoder st → Encoder LostStream ∨ Encoder AnyFrameExpected

feedNonKeyframe :: Frame → Encoder AnyFrameExpected → Encoder LostStream ∨ Encoder KeyFrameRequred ∨ Encoder AnyFrameExpected
-- или, если обмазываться завтипами
feedNonKeyframe :: Frame → Encoder AnyFrameExpected → Encoder LostStream ∨ Σ st. (Encoder st ∧ CanFeedKeyFrameAt st)

recover :: Encoder LostStream → Maybe (Encoder KeyFrameExpected)

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

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

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

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

Более того, есть либы вроде https://hackage.haskell.org/package/lvish , которые дают мутабельность, параллельность и детерминизм.

Во-первых, сама задача не очень богата на бизнес-логику, тестируемую в изоляции от внешнего мира. Условно, писать шелл-скрипты на ФП тоже не то чтобы демонстрирует все его прелести, а тут всё сводится к дёрганью внешних API.

Ну автор же просил реальный пример где ООП нужно, я его привел. Это боевая задача, реальный проект. И да, там много что сводится к дерганью внешних API, потому что видео фреймы приходят от ОС интерфейса камеры, а потеря пакетов осознается при получении пакетов по сети. Рисование на экране происходит по таймеру Vsync, скрипт выполняется по щелчку мыши по кнопке...

Я не спорю, что есть примеры, где ФП заиграет всеми красками и что оно обладает там удивительнейшими приемуществами. Но если вы пишите не число дробилку - это не так уж и часто.

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

А как вы тут запишите, например, что у H264HWEncoder (и только его из всех 4) нельзя 2 раза подряд кодировать key-frame (нельзя иметь последовательность request kf, encode, request kf, encode, и нельзя иметь request kf перед вторым вызовом encode). И что в оба HW encoder нельзя передавать видео фреймы размера меньше 240x180? А у второго HW енкодера надо обязательно хотя бы раз 100 Encode вызывать RequestKeyFrame. И что у вас в системе не более 3 HW encoder создается. И вот это вот все вместе.

разные инстансы энкодеров можно было бы дёргать параллельно независимо друг от друга

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

у вас вначале в самом верху не было написано что они могут или должны работать одновременно я понял как что есть задача про енкодеры и вы там писали про класс с енкодерами на ООП

тогда это не ООП а просто реализовать запись паралельную из 1 программы где вы кликаете на тип енкодера

автор статьи пытался изложить что ООП может нагрузить/переусложнить/оверинжиниринг вот это вот всё, и по итогу проблем больше чем просто реализовать задачу

10 енкодеров 10 структур есть чек бокс кликаете в гуе выбирается енкодер, соотв не факт что это ООП

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

Так ведь не нужно. Я вон без всяких ООП написал, и даже без смущающего слова class. Просто функции, функции, композиции функций. Классика ФП.

Но если вы пишите не число дробилку - это не так уж и часто.

Компиляторы — числодробилки? Их парсерная часть? Или те же перекладывалки жсонов уровня «запуститься по крону, взять данные из трёх БД, заджойнить по хитрой бизнес-логике и сложить в четвёртую»?

А как вы тут запишите, например

Начну с конца, с самого скучного.

И что у вас в системе не более 3 HW encoder создается.

Просто возвращая Maybe Encoder (или Either EncoderCreationError Encoder), а не Encoder. Это существенно нелокальное свойство, и создание энкодера может навернуться по куче других причин (включая софтварные): три HW encoder'а, или видяха из слота вытащилась (PCIe же поддерживает hotplug?), или для софтваре энкодера dlopen для x264 чё-т не работает. Я не вижу смысла выражать конкретно тройку конкретно в типах (но если вам очень хочется, то могу показать, как это делать).

А у второго HW енкодера надо обязательно хотя бы раз 100 Encode вызывать RequestKeyFrame.

С зависимыми типами можно выразить что угодно.

data State
  = ...
  | CanFeedNonKeyFrameCounted Nat -- параметр — оставшееся число не-кейфреймов
  
data EncoderMetadata = EncoderMetadata
  { maxNonKeyframes :: Maybe Nat
  }

-- есть более изящные способы, чем таскать `md` в типе,
-- но для маленького примера они не нужны
data Encoder md st = ...

loadVP9hwEncoder : IO (Encoder (EncoderMetadata (Just 100)) Initialized)
loadVP8hwEncoder : IO (Encoder (EncoderMetadata Nothing) Initialized)

feedNonKeyframe : Frame
                → Encoder md (CanFeedNonKeyframe (suc n))
                → Encoder md (CanFeedNonKeyframe n)
                
feedKeyframe : CanFeedKeyframeAt st
             ⇒ Frame
             → (e : Encoder md st)
             → Encoder md (case maxNonKeyframes md of Just n ⇒ CanFeedNonKeyframeCounted n
                                                      Nothing ⇒ CanFeedNonKeyframe)
-- или, если это решается динамически, тогда `Maybe` у maxNonKeyframes не нужен
-- и возвращаем тип-сумму из двух вариантов, по которым надо будет сделать рантайм-матч:
             → Encoder md (CanFeedNonKeyframeCounted (maxNonKeyframes md)) ∨ Encoder md CanFeedNonKeyframe

И что в оба HW encoder нельзя передавать видео фреймы размера меньше 240x180?

Совершенно аналогично:

data EncoderMetadata = EncoderMetadata
  { maxNonKeyframes :: Maybe Nat
  , minFrameSize :: Size
  }

feedNonKeyframe : (f : Frame)
                → Encoder md (CanFeedNonKeyframe (suc n))
                → {auto 0 _ : minFrameSize md ≤ frameSize f}
                → Encoder md (CanFeedNonKeyframe n)

...

у H264HWEncoder (и только его из всех 4) нельзя 2 раза подряд кодировать key-frame (нельзя иметь последовательность request kf, encode, request kf, encode, и нельзя иметь request kf перед вторым вызовом encode)

Совершенно аналогично (только в моём словаре нет encode, поэтому в виде кода я выражать не буду, но смысл понятен, надеюсь).

А если все енкодеры во время работы что-то пишут в общий логгер?

Значит, они должны будут жить в IO, и будет сразу ясно, что с многопоточностью может быть ерунда.

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

Я вон без всяких ООП написал, и даже без смущающего слова class

Тем не менее, у вас там тоже есть штука "Encoder" которая описывает набор функций, в точности как интерфейс в C++. У вас есть конкретные его реализации для каждого конкретного кодека, а так же штука, что модифицирует их поведение делая fallback. При чем эти штуки, хоть это и не C++ классы, а замыкания, являются набором кода и данных, с которым он работает. Если вы хотите эти данные как-то поменять снаружи, вы вызываете лишь выставленные наружу функции, а не меняете его прямо, ровно как работает инкапсуляция в классах.

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

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

У вас точно такое же логическое деление на каждый конкретный энкодер (функция делающая, например, vp8 software), у вас есть какой-то общий тип функции для всех энкодеров, у вас есть swFallbackEncoder, который держит логику отката и выглядит снаружи ровно как encoder. Вы точно также добавили бы DumpingEncoder для сливания данных в файл, если он нужен.

Вы отказались от class/instance потеряв какую-то часть проверок, ведь теперь вы можете в коде в качестве Encoder использовать и условное замыкание для шифрования пикселей, у которого по случайности и недосмотру главный метод также обозвали Encode(). Если вы добавите их, то вам компилятор еще и проверит, что вы в качестве encoder используете именно то, что вы обозначили, как являющиеся им. И ваше решение в общем-то останется точно таким же.

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

С зависимыми типами можно выразить что угодно.

Я понимаю, что любое конкретное ограничение для конкретной реализации Еncoder можно относительно легко записать. Но как это работает, когда у вас есть "интерфейс" Encoder? У вас же mkEncoder имеет конкретный тип - он возвращает Encoder. Если вы начинаете вот так вот расписывать возвращаемые типы для условного h264HWEncoder и остальных, это же отражается как-то на описании Encoder? И вот когда вам надо все эти ограничения для всех разных реализаций вместе записать - это очень громоздко и экспоненциально же.

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

Да. Только это означает, что я пользовался инкапсуляцией. Говорить, что у ООП монополия на понятие инкапсуляции — это перебор (а мы до этого дойдём через несколько итераций).

у вас там тоже есть штука "Encoder" которая описывает набор функций, в точности как интерфейс в C++

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

но в виде общего Encoder в точности как фабричный метод.

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

В точности как полиморфизм в ООП.

Нет, потому что в ООП навешаны какие-то принципы подстановки лисков (которые не работают), виртуальные функции, рантайм-диспатчинг, и так далее, а в ФП я просто передаю функции в функции, и полиморфизма здесь не больше, чем полиморфизма в функции int foo(int n) { return n + 2; }. которая игнорирует конкретное значение n. Как обычно.

Вы точно также добавили бы DumpingEncoder для сливания данных в файл, если он нужен.

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

Вы отказались от class/instance потеряв какую-то часть проверок, ведь теперь вы можете в коде в качестве Encoder использовать и условное замыкание для шифрования пикселей, у которого по случайности и недосмотру главный метод также обозвали Encode().

Нет, не могу. Имена и их совпадения важны только для class/instance. Если я от них отказался, то важны только типы.

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

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

И говорить, что «дизайн системы в ФП отражает концепции из ООП» — ну да, а чего вы хотели, если там есть инъекция? Это как говорить, что «чего вы говорите, что вещественные числа полнее целых, если при решении 2x = 4 вы не пользовались вещественными числами?»

Но как это работает, когда у вас есть "интерфейс" Encoder? У вас же mkEncoder имеет конкретный тип - он возвращает Encoder.

Тогда mkEncoder возвращает Encoder и его «метаданные» через Σ-тип (то есть, пару, где тип второго компонента зависит от значения первого), например:

mkEncoder : String
          → IO (Σ md. Encoder md Initialized)
mkEncoder "vp9" = do
  hw ← loadVP9hwEncoder
  sw ← loadVP9swEncoder
  pure $ (_ , mkFallbackEncoder hw sw)

где я даже не указываю явно значение первого компонента пары, потому что тайпчекер его успешно выводит из второго. И, кстати, здесь же используется mkFallbackEncoder:

_⊓_ : EncoderMetadata
    → EncoderMetadata
    → EncoderMetadata
md1 ⊓ md2 = EncoderMetadata
              (maxNonKeyframes md1 ⊓ maxNonKeyframes md2)
              (minFrameSize md1 ⊔ minFrameSize md2)

mkFallbackEncoder : Encoder md1 Initialized
                  → Encoder md2 Initialized
                  → Encoder (md1 ⊓ md2) Initialized
mkFallbackEncoder = ...

где эти смешные значки ⊓ и ⊔ — это решёточные greatest lower bound и least upper bound, которые для количества кейфреймов сводятся к минимуму двух значений, а для минимального размера фрейма — к максимуму из двух.

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

Мне даже тестировать ничего не надо. Типы помогают ловить ошибки на этапе описания самих типов и помогают убедиться, что описываемая логика внутренне консистентна.

Признаю свою неправоту, функциональные языки невероятно сильны.

Чтобы ты опять написал "ааа, ыыы, моя не понимать что тут написано"?

Да, действительно такой пример есть. Это UI основанный на оконной модели. Под эту задачу ООП расцвел и стал применяться где надо и где не надо. Могу привести статью, где автор рассказывает где ему пришлось в библиотеке для UI на **функциональном** языке точечно применить ООП подходы.

в современных тенденциях

Эти современные тенденции закончились лет 10-15. Сегодня модно ООП поливать дерьмом, во многом заслуженно.

У ООП много неприятных особенностей, но понимание, что мода на него прошла приходит тогда, когда его ругают те, кто в нем ничего не понимает :)

Да я только осознал, что в FizzBuzzEnterpriseEdition 8 (!) лет не было коммитов. Это настолько обсуждаемая статья устарела.
А пик моды на ООП, наверное, вообще пришёлся на конец 90-х. :)

Думаю статью минусуют за очень агрессивную подачу своего мнения( особенно в контексте джавистов и 99% человечества), мне кажется автору стоит немного пересмотреть эти абзацы и в целом отвлеченные от кода моменты статьи в сторону более нейтральных формулировок. На всякий случай напишу, что тут автор не прав именно по форме, а не по содержанию - там его мнение может быть какое угодно.
А по сути статью - я больше согласен, чем не согласен. Сам когда-то пытался засесть в ООП и после знакомства с плюсами даже чистый Си мне показался лучше, в итоге плюнул и ушёл на Python. Очень большая перегрузка ключевыми словами, необходимый "обвяз" к каждому классу в виде конструктора/деструктора, по две пары методов на каждое поле и негибкость в моментах обработки бьет(сам чаще всего пишут в функциональном подходе, на классах только ORM). Я бы от себя ещё добавил, что инициализация и создание класса как операция чаще всего стоят больше, чем вызов функции, а иногда и по памяти бьют сильнее, в результате чего можно получить хуже работающий код.
Но чтобы прям всё ООП плохо это тоже слишком сильный тейк. Когда-то с коллегами дошёл до вывода, что Java крепко держит средний и большой сегмент разработки за счёт двух вещей: универсальной компиляции на любых устройствах и самодокументируемости интерфейсов(если вести их согласно адекватности и рекомендациям).

Я к минусам отношусь спокойно, и разрешаю себе говорить то, что думаю. Вот в коллективе да, речь фильтрую конечно, софт скилы, все дела.

Тут не больше про минусы, а про сам факт её восприятия - если хотите убедить людей, что ООП плохо, то не стоит это делать в такой форме, когда многие не согласятся с вашим мнением исключительно из-за формы подачи. Можно говорить всё, что думается, но говорить это так, что даже за самые большие грубости в нос не получишь :)
А так из статьи могла выйти хорошая дискуссия и диалог, который смог бы переубедить некоторых людей пересмотреть свой подход к коду. А из-за некоторых моментов часть комментариев направлено не на содержание статьи и диалог, а на агрессию и соответственно ответную агрессию на вас, как на автора.

универсальной компиляции на любых устройствах

А TypeScript запускается не на любых устройствах?

самодокументируемости интерфейсов

Вы удивитесь, когда узнаете про какой нибудь GraphQL на TypeScript, с автогенерацией не только типов модели и сайтом с полной документацией и возможностью запуска запросов, но и кода доступа. Java и рядом не валялась.

Но чтобы прям всё ООП плохо это тоже слишком сильный тейк

Тогда должен быть хоть один пример кода, где ООП выигрывает ФП. Но что то его все нет и нет.

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

Сам когда-то пытался засесть в ООП и после знакомства с плюсами даже чистый Си мне показался лучше, в итоге плюнул и ушёл на Python

Python это же ООП в терминальной стадии. В чём логика ваших метаний?

необходимый "обвяз" к каждому классу в виде конструктора/деструктора

Не необходимый, вас кто-то обманул.

по две пары методов на каждое поле

Тривиальные геттеры/сеттеры на каждый чих это антипаттерн так-то.

Python это же ООП в терминальной стадии.

Чего? Я бы понял, если бы это было сказано в контексте Java или Шарпа, где всё остальное просто выпилено и у вас нет никакого выбора, кроме классов. В Python вы пишите как хотите(но чаще всего в рамках coding convention вашей компании), у него нет приставки "Объекто-ориентированный язык". ООП опционально, если кому-то очень сильно надо.
Суть моих метаний только в том, что после пробы функционального подхода я не смог влиться в ООПшный СИ++ подход(во время обучения другие в нём не котировались, но я в курсе, что там тоже можно иначе), в итоге ушел в динамический язык, где использую этот же функциональный подход. Не знаю, что тут может быть непонятно.
Мне очень интересно, что вы считаете "ООП в терминальной стадии", хотелось бы пояснение.

Не необходимый, вас кто-то обманул

Тривиальные геттеры/сеттеры на каждый чих это антипаттерн так-то.

Это смешно слышать, когда в половине IDE есть отдельная функция по генерации этой пары функций для класса и генерации класса из имени файла СРАЗУ ЖЕ с const/dest, настолько оно повсеместно. По крайней мере скок вижу ООП-проекты даже в рамках компаний, там везде натыкано их уйму, даже если к этому полю только обращения чтения и оно один раз задается при создании ¯\_(ツ)_/¯ .
Я думаю есть хорошие разработчики, которые пишут правильное ООП без всего лишнего, но их соотношение непропорционально с теми, кто пишет на ООП потому что так принято и делают подобные вещи просто потому что им так подсказывает среда/так надо/в проекте уже так написали миллион строк, надо писать так дальше. Я не буду утверждать, что ООП как сам факт плохо, плохо его нерациональное использование везде где только можно, когда есть подход лучше в конкретной ситуации и что парадигму надо выбирать из задачи, а не пытаться каждую задачу пихать под парадигму.

В Python вы пишите как хотите

Вы не поверите, но на С++ тоже.

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

Я могу понять, что вам не понравился синтаксис плюсов (он в самом деле на любителя), но ООП тут при чём?

Это смешно слышать, когда в половине IDE есть отдельная функция по генерации этой пары функций для класса и генерации класса из имени файла СРАЗУ ЖЕ с const/dest, настолько оно повсеместно. По крайней мере скок вижу ООП-проекты даже в рамках компаний, там везде натыкано их уйму

Понятно. Я просто учился программировать не по IDE и не по чужому говнокоду, а по хорошим учебникам. Поэтому не пихаю конструкторы/деструкторы и геттеры с сеттерами туда, где они не нужны. А о ненужности их пихания говорится, по-моему, в каждом учебнике по С++.

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

О! Пришли к необычайно сложной и недоступной мысли, что злом является не инструмент, а неадекватное его использование. Кто бы мог подумать :)

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

У меня ощущение, будто вы мои комментарии по диагонали читаете частично.

Вы не поверите, но на С++ тоже.

Это есть в прошлом комменте, в скобках.

О! Пришли к необычайно сложной и недоступной мысли, что злом является не инструмент, а неадекватное его использование. Кто бы мог подумать :)

Это есть даже в изначальном, где я утверждаю, что тейк "ООП=зло" это явный перегиб, а потом ещё раз вам повторяю, чтобы мы точно заметили.

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

Об этом факте я в курсе, но имхо - если вы не пользуетесь метаклассами(т.е не разраб фрейморков/ORM/библиотек или не изврат, который любит шалить в коде)для массового изменения поведения объектов кода, то этот факт на вас не влияет принципиально. По этой же причине в сознании многих Python != ООП-язык. В этой статье есть хорошее объяснение в комментарии(НЕ В САМОЙ СТАТЬЕ), как оно работает от danilovmy https://habr.com/ru/articles/806545/ и почему оно не для всех.
ИМХО, тот случай, когда ООП идеально встало на своё место.

Понятно. Я просто учился программировать не по IDE и не по чужому говнокоду, а по хорошим учебникам. Поэтому не пихаю конструкторы/деструкторы и геттеры с сеттерами туда, где они не нужны.

Это просто факт, который говорит о том, что таковы тенденции БОЛЬШИНСТВА, раз это пихают на уровне автоматики. Вы, может быть, и молодец, а три Васяна-джуна делают именно так, а умные люди, сделавшие среду разработки, им в этом помогают и поощряют. Оттого потом начинается хаос и т.д. . Ещё раз - я не утверждаю, что так делают все, но есть в этом достаточно крупная тенденция, которую сложно устранить, особенно в эпоху онлайн-курсов.


Подведу наверное финально-финальный вывод:
1) ООП не зло в чистом виде. В этом я не согласен с автором.
2) ООП можно использовать правильно там, где .... его можно использовать правильно. Капитан очевидность.
3) Использовать ООП везде очевидно неправильно. Капитан очевидность №2.
4) Использовать неправильно можно вообще любой подход. Поэтому надо знать меру и использовать адекватность.
5) С вашей мыслью, что хейт от незнания я не согласен. У каждого свои причины для ненависти :) я не люблю ооп, потому что его насильно насаживают там, где есть методы эффективнее(имхо). У автора видимо какая-то непосредственная ненависть в Java-разработчикам, у кого-то будет третья причина. Хейт из популярности и обсуждаемости, т.к много людей = много мнений.
6) ООП неидеально, по крайней мере в том, как его используют сейчас. И есть вещи, которые в нем стоит критиковать для его же блага. Вполне возможно, что автор указал не на те из них по мнению самих ООП-шников, но автор хотя бы попытался подсветить проблему его повального бездумного использования, просто явно не в той форме.
Так что мы с вами топим за одно и то же, просто я совсем ушел от ООП в пользу другого инструмента, но не поставил на нем жирный крест, а вы пытаетесь его облагородить.

Ну тут такой момент, что не в том дело насколько язык ооп, а В КАКОЙ ПАРАДИГМЕ принято/пишут на этом языке. Никто не потащит плюсы в проект, чтобы писать на нем в фп/пп парадигме, и наоборот питонисты в 99% не пишут в ооп парадигме.

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

У меня было бы нуль вопросов к статье, если бы она была именно что про "не суйте ООП туда, где он не нужен". Но автор начал крестовый поход против ООП как такового, что по мне так неадекват полный. Всё равно что с синглтонами воевать, наплевав на контекст.

Только большинство примеров изначально некорректно написаны. Т.е. код написан плохо на ООП, а затем героически решается проблема на ФП или каком-то другом аналоге ООП.

Большинство примеров, где ООП "проигрывает", достаточно переписать нормальным образом и проблемы уходят.

Ну так перепишите и убедите, я свой код скинул. А без кода это демагогия.

Да пожалуйста:

Вот ваш код:

class User {
  firstName: string
  lastName?: string
  middleName?: string
  ... // Другие поля, не нужные для getDisplayName.

  constructor(firstName: string, lastName?: string, middleName?: string) {
    this.firstName = firstName
    this.lastName = lastName
    this.middleName = middleName
  }

  // Метод.
  getDisplayName() {
    return [this.firstName, this.middleName, this.lastName]
      .filter(Boolean)
      .join(" ")
  }
  
  ... // Другие методы, не нужные для getDisplayName.
}

// Функция.
const getDisplayName = (user: {firstName: string, lastName?: string, middleName?: string} | null | undefined) => {
  if (!user) return undefined

  return [user.firstName, user.middleName, user.lastName]
    .filter(Boolean)
    .join(" ")
}

// Еще более гибко, но может быть менее удобно.
const getDisplayName = (firstName: string, lastName?: string, middleName?: string) => {
  ...
}

Вы критикуете ООП то, что в класс добавляются без конца методы, "приколочены", необходимость class extension, как костыль в шарпе и показываете, что типа функция - это круто. Отделение функциональности от данных.

Ваши придирки давным-давно разобраны Дядей Бобом. Знаете, кто это? Так узнайте, почитайте его книги. И более того, реализовано в Spring и прописано в SOLID.

Для решения вашей проблемы делается следующее.

Создается класс User

public class User {
  private String firstName;
  private String lastName;

  ... Здесь конструктор
  ... Здесь геттеры прямо либо через ломбок
}

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

public class DisplayFormatter implements Formatter {
  public String format(User user) {
    return user.getFirstName + " " + user.getLastName;
  }

  public String format2(User user) {
    return "Имя: " + user.getFirstName + ", Фамилия: " + user.getLastName;
  }
}

До кучи и при необходимости добавляем классы WebFormatter, RtfFormatter и т.д.

далее используем:

publi class SomeClass {
  private Formatter formatter = ... здесь dependency injection 
   любым доступным вам способом
    
  public SomeMethod() {
     User user = ...
     String result = formatter.format(user);
  }
}

Вот ЭТО - - гибкость (благодаря DI).

В результате набивается в дальнейшем функционал без разбухания и изменения User, что вам и хочется иметь.

Во-первых, ООП-шник вы достаточно рукожопый, так как реализуете полиморфизм в DisplayFormatter через класс (требуете тип User, а не интерфейс).

Во-вторых, почему то из типа User пропали другие поля и методы что в нем есть. Это второй User только с двумя полями firstName и lastName? Или все тот же User, и получается что Formatter зачем то требует целиком тип User со всем что у него есть, используя всего лишь firstName и lastName? Получается его нельзя переиспользовать, например, для собаки у которой тоже есть firstName?

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

Ты скажешь, что конечно изначально бы так (деpьмово) спроектировал с форматтером, но! Вопрос остался открытым, ты просто от него ушел. Как переиспользовать метод одного класса в другом, без проблемы банана и макаки, не подтягивая лишнее?

Ты же можешь себе представить хоть один метод хоть в одном классе? Вот как его переиспользовать в другом? Например метод Add, и только его, из ArrayList и каком нибудь BetterArrayList?

Для ФП пример getDisplayName для собаки приведен, и там все элементарно.

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

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

В Java даже специально ввели record для этого

public record ИмяRecord(Тип1 поле1, Тип2 поле2, ...) {
    // Дополнительные методы или конструкторы (если нужно)
}

Интерфейс Formatter работает только с User. Что значит "Переиспользовать для собаки" - вы угораете? Formatter работатает ТОЛЬКО с User - в этом и заключается контракт. User должен печататься ровно одним способом, как определит бизнес-аналитик (не вам решать). Собака печатается совершенно другим способом (учитывая еще развилки для WebFormatter и RTFFormatter).

Что значит "получается что Formatter зачем то требует целиком тип User со всем что у него есть, используя всего лишь firstName и lastName?"? User - это цельный законченный класс, он передается через указатель любому классу и методу, который его обрабатывает, без потрошения и изменения. Вы каждый раз распаковываете поля и передаете методу, который что-то делает с информацией из User? Зачем?

Самое главное - если потребуется поменять набор полей, добавить, например "Обращение", ("Сэр", "Леди") в User и изменить метод печати, то в моем примере, меняется только классы User, и реализации format (Интерфейсы не меняются), и SomeClass тоже не меняется. А в вашем случае придется менять ВСЕ вызовы в приложении, а их тысячи может быть. То есть, детали реализации User и печати проникают в тысячи других классов всего приложения. Не надо так.

Вообще, я не понимаю, что вы понимаете под словом "Переиспользовать"?

Ты же можешь себе представить хоть один метод хоть в одном классе? Вот как его переиспользовать в другом?

Никак. Именно для этого методы и привязываются к классам. Чтобы их нельзя было использовать с другими структурами данных, на работу с которыми метод не рассчитан. Если вам для какого-то метода это не подходит, значит неправильно, что вы поместили его в этот класс, значит он должен быть где-то в другом классе. ООП не требует, чтобы GetDisplayName() был именно в классе User.

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

Зачем для этого класс? Какой у него стейт? Могу ли я один инстанс класса DisplayFormatter дёргать из разных тредов? Могу ли я разные инстансы дёргать из разных тредов? Эквивалентен ли DisplayFormatter, отформатировавший сто юзеров, свежесозданному?

А можно просто сделать чистую функцию formatUser : User → String, и там этих вопросов нет. И передавать эти функции как аргументы другим функциям (если очень хочется звучать умно, можно это тоже называть DI).

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

Самое главное, как вы будете определять, какую функцию передавать в другую функцию в случае, если например, для одних настроек приложения надо использовать DisplayFormatter, а для других - RTFFormatter? В Spring и вообще при применении DI фреймворк создает нужный инстанс и инжектит его в классы потребителей (в SomeClass) - в поле с интерфейсом Formatter присваивает нужный инстанс.

В DisplayFormatter нельзя добавлять поля, которые используются в бизнес-логике

Кто запретит? Компилятор?

только инфраструктурную информацию, типа подключения к базе данных, логер и настройки из yml (кстати, как вы будете хранить эту информацию в ваших чистых функциях?)

Какое ещё подключение к базе данных в классе (или функции, неважно), отвечающем за форматирование имени данного ему юзера? Это чтобы макаронный код потом веселее разгребать было?

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

Так и буду.

data Options = Options
  { useRtf :: Bool
  } deriving (Eq, Show, Generic, ParseCliOptoins)

main :: IO ()
main = do
  Options{..} ← getCliOptions
  doThings (if useRtf then rtfFormatter else displayFormatter)

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

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

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

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

А надо-то всего-навсего полочку повесить.

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

Отказоустойчивость форматтера — это, конечно, важная тема. А как узнать, что он при этом не сохраняет что-нибудь в БД и не выставляет какие-нибудь флаги третьему процессу, что было добавлено шесть лет назад при попытке починить критический срочный баг в продакшене в три ночи в пятницу вечером?

Отказоустойчивость форматтера — это, конечно, важная тема.

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

А как узнать, что он при этом не сохраняет что-нибудь в БД и не выставляет какие-нибудь флаги третьему процессу

В код посмотреть. Я в курсе всех ваших аргументов, а вы в курсе того, что компайл-тайм гарантий чистоты в эрланге нет. Не вижу смысла перетирать всё это по стопиццотому кругу.

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

Можно ли считать цепочку постов реализацией наследования? 🤔

Скорее уж блокчейном.

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

Главное, чтобы не случилось инцеста ромбовидного и кто-то не родил ООП-ФП монстра

Пока что даже одного "правильного" куска кода не набралось. Ждем, не дождемся.

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

Это не ранимость, а самоуважение.

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

Если вам нормально носить штаны на голове,то совсем не обязательно,что всем остальным будет тоже удобно. Полистать вниз - увидите достаточно комментариев людей,которых статья оттолкнула ИСКЛЮЧИТЕЛЬНО из-за формы написания.

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

Тут ведь как: если человек задаёт вопрос кому-то и ему принципиально важно его мнение - он может приложить усилие и отфильтровать суть от подачи.

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

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

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

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

Для меня это чем-то напоминает западную повестку.

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

Ну ладно _)))

И как вы без классов и полиморфизма предлагаете реализовывать, например, поддержку объектных файлов - когда есть сущность со специфичными для неё задачами (пробежаться по секциям, посмотреть архитектуру и т.п.), для которых частично применимы какие то общие алгоритмы, но для серьёзной работы нужен отдельный код (скажем, для поддержки ELF на Linux, PE на Windows и т.д.)?

Полиморфизм прекрасно живет в ФП.

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

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

Зачем вы это мне написали? Я отвечал на:

как вы без классов и полиморфизма предлагаете […]

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

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

Видимо слишком революционные идеи :D

ECS это просто паттерн, который прекрасно сочетается с ООП, тут нет противопоставления. Это как заявить что MVC или MVVM это замена ООП

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

DTO и хэндлеры. Так можно сказать что CQRS с ООП не сочетается.

Про CQRS разрешаю, говорите.
ECS по моему опыту предлагает кардинально другой подход к проектированию систем, так что с ООП он сочетается разве что на уровне "вот эту часть программы пишем на ООП потому что на нем удобнее делать ГУИ\FSM\общаться с ООП-библиотекой, а все остальное на ЕЦС".

Ппц, опять безумцы воюют с инструментами, когда надо бы воевать с неадекватным использованием инструментов 🤦🏻‍♂️

Воистину. Нужно повышать общую (и собственную) грамотность и рационализм, а не устраивать холивары.

Так и знал, что будет комментарий про "кривые руки" и тп. Так приведите примеры кода, где якобы правильно все сделано. Я свой код привел. Без кода общение не конструктивно.

Перепишите GTK в процедурной парадигме, делов-то.

А делать глобальные выводы из каких-то высосанных кусочков кода - это надо иметь кривые мозги, а не руки.

Я мог бы согласиться с некоторыми тезисами в статье, но сильно отталкивает подача. Буквально не хочется ассоциироваться с автором и говорить "он прав", не добавляя при этом "но он ...ак".

За 13 лет автор ни ООП не освоил (привет "истинному" полиморфизму с копипастой свичей), ни ФП (привет замыканиям с изменяемым состояниям).

Talk is cheap, show me the code (c).

Я там выше показал уже код. С нетерпением жду вашего экспертного заключения.

Функциональное программирование (ФП) — это программирование используя структуры и функции. Не путать с функциональным стилем.

Это называется процедурное программирование. Так, к сведению.

И я даже сделал секцию Частые возражения, где именно это возражение прокомментировал.

Но зато часто встречается понятие "функциональное программирование". И причем это как раз и есть - парадигма. А не какой-то там "стиль".

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

Вы тут сами запутались. Для ФП принципиально важно, чтобы были first-class functions и HOF, без этого действительно получится ПП

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

И что там? Половина комментаторов вслед за ним называет процедурное программирование функциональным. Вторая - комментирует так, как будто его статья реально про ФП ("математический стиль" в альтернативно-одаренной терминологии автора).

Спасибо вам за этот комментарий! Я до него доскроллил, и моя вера в здешнюю публику не умерла окончательно.

Повторяю, я ваше мнение про "это процедурное" даже отдельно разобрал в статье.

Альтернативно-одаренным нужно внимательнее читать.

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

А математический стиль - куда более правильное название как по мне, потому что понятие "функция" там используется из математики.

Это большинство с вами в одной комнате?

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

Вы либо галлюционируете, либо жырнейше троллите.

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

Самый популярная библиотека самых популярных языков - React, давно перешла на ФП. В Linux - ФП. Основа говнокода разве что, но про это и статья, и обложка статьи.

В Linux - ФП.

Разрабам Линукса об этом сообщите, а то они не в курсе и какие-то странные вещи пишут в статьях: https://lwn.net/Articles/444910/

И хватит уже называть процедурное программирование функциональным, это кринж.

В своей статье я четко даю понять что считаю ООП - наличие классов с наследованием и тп. А связывание данных и методов теми же замыканиями - не считаю. В Си классов нет, точка.

Да-да, те самые легендарные чистые хуки из реакта, которые нельзя использовать в ветвлении, иначе всё сломается.

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

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

Ну про линукс уже ответили... А react... это - десятки, ну пусть сотни тысяч строк кода в проекте.

А что насчет систем с миллионами и десятками миллионов строк кода и временем жизни десятки лет?

Они, как правило, написаны на Фортране и Коболе.

Вряд ли на фортране можно написать достаточно крупную систему.

Код бортового компьютера миссии «Аполлона-11»
Код бортового компьютера миссии «Аполлона-11»

GitHub

На фортране было бы даже проще, если бы он существовал к тому времени.

Посмотрел. Какой же ужас

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

А по-моему, здорово.

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

Этот код, между прочим, космический корабль на Луну посадил, а не пиццу заказывает с обрывами связи и сбросами настроек.

Что не делает его большим.

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

А если кто-то гениальный (неиронично) напишет идеальный код на JS, который посадит космический корабль на Марс, то всё, теперь все обязаны будут признать, что JS идеален?

Кто и где делал глобальные выводы о языке?

Я, вроде бы, отвечал на вопрос:

Реально было бы желание с этим разбираться? Вместо того чтобы читать самодокументированный код на языке высокого уровня?

Реально было бы желание с этим разбираться?

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

Тем не менее, потом как раз для подобного придумали язык ADA. Строго типизированный язык высокого уровня.

очередной жирный каличный веб-сервис

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

Фортран-то к тому времени уже сменил 4 версии и вошёл в пору своего расцвета, но бортовые компьютеры его не тянули.

Фублин, ну да, разумеется. Спасибо за внимательность.

Небезызвестный американский брокер Bloomberg, например, использовал систему из 25 миллионов строк на Фортране. Вроде, до сих пор пытаются переписать.

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

Вообще-то современный Фортран имеет одну из лучших модульных систем среди всех языков программирования (и уж точно намного лучше C++), поэтому хорошо приспособлен для написания больших программ.

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

Причём в одной и той же программе можно использовать и модули в новом, и в старом стиле, то есть просто раздельно транслируемые объектные файлы, как в Си. Так что модуль, написанный в 1964 году, нет никаких проблем подключить.

Описание Фортрана 2008

Да вопрос вообще в другом как я понял из статьи. А нужно ли ООП в 99% современного ПО если там спокойно можно обойтись ФП (в терминологии автора, я то понимаю что это ПП по академическому)? И возможно популярность ООП результат маркетинга и массового обучения ему начиная со школы? Ну вот как сейчас помню эту ахинею про то что "ооп оказался революционным подходом в создании программ так как является отражением реального мира". "Чиво блять?"

Что подразумевается под 99% современного ПО? Например, какие-нибудь CAD-системы входят в это множество?

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

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

Я вам больше скажу. В современных практиках максимально отходят от наследования и заменяют их композицией. SOLID и Spring - именно про это. Бины в Spring - это, по сути, ссылки на модули в терминологии ПП, в которых определены методы для обработки объектов определенных Record-ов.

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

Так думаю:

//файл user.pas
record User 
begin
...
end;

//файл formatter.pas
interface Formatter 
begin
  format(user: User);
end;

//файл DisplayFormatter.pas
module DisplayFormatter: Formatter 
begin
  format(user: User)
  begin
    //печатаем
  end;
end;

//файл SomeClass.pas
module SomeClass
var formatter: Formatter; //Здесь система должна сама подставить
begin
  someMethod()
  var 
    user: User;
    fullName: String;
  begin
    user := ... откуда-то берем
    fullName := formatter.format(user);
    ... дальше используем fullName
  end;
end;

Вот такая система была бы просто бомба.

Это близко не Pascal и я вообще не понял вашу задумку. Если у вас несколько реализаций для formatter, то правильнее будет просто иметь один модуль с абстрактным форматером, который доступен где нужно. А его реализация определяется тем, что вы проинициализируете при старте (на основе параметров запуска или просто кодом)

Вдобавок ко всему, именно класс юзера должен реализовать своё форматирование, а не реализация форматера. Т.е. на юзера навешиваем интерфейс, что он может форматироваться. А форметтер принимает ссылку на объект и использует методы интерфейса для форматирования как ему нужно, на основе реализации юзера

именно класс юзера должен реализовать своё форматирование

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

Если каждый класс должен выходить по-своему, то да.

Если посмотреть на историю индустрии и что происходило в разные ее исторические периоды, то фактически вы полностью не правы. Именно ООП дало принцпиальный толчек к революционному переходу, когда появилась возможность резко повысить сложность и объем ПО. Для этого достаточно вспомнить первый ДОС, консольные утилитки, простые программки и потом визуальные среды оконной разработки (для дос типа турбовижн или вижуал фокспро) и под виндовс. Все это ООП, но правда не тот трешевый, что сейчас, а теплый ламповый С++ с классами. А ФП с точки зрения цифровой археологии не дал вообще ничего заметного "народному хозяйству", хотя вспоминается лисп как скрипт язык в автокаде.

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

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

Talk is cheap, show me the code (c).

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

Книга Gof?

https://ru.wikipedia.org/wiki/Design_Patterns

" Книга состоит из двух частей, в первых двух главах рассказывается о возможностях и недостатках объектно-ориентированного программирования, а во второй части описаны 23 классических шаблона проектирования. Примеры в книге написаны на языках программирования C++ и Smalltalk. "

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

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

Но даже со всем этим вашим хиндли-милнером. Посмотрите, чего понапихали в окамл и в хаскелл... Не потому что могут, а потому что нужда заставила. Функторы и тайпклассы, монады и линзы там всякие. ФП <-> ООП для бедных.

Пожалуйста.

class Dog extends Animal {
  firstName: string
  lastName?: string
}

({firstName: "Alexander"}).getDisplayName()

let user: User | null
user.getDisplayName()

Или вот.

class User {
  id: string
  name: string
  surname: string
  address: string
  friends: User[]

  constructor(name: string, surname: string, address: string, friends: User[]) { … }

  getDisplayName() { … }

  hasFriend(id: string) { … }
}

class Npc {
  id: string
  name: string

  constructor(name: string) { … }

  getDisplayName() { … }
}

Понятно, что я имею в виду, или вам всё-таки talk нужен?

Смоделируйте, что понадобилось добавить печать в rtf. И в одной конфигурации приложения надо вызывать один метод, а в другой - другой? А если в разные моменты разные методы? Сколько файлов пришлось перетрясти? А если один разраб будет реализовывать для rtf, а другой для html?

А также ObjectPascal и последовавший за ним Delphi. А вдохновителем - Simula и SmalTalk.

А ФП с точки зрения цифровой археологии не дал вообще ничего заметного "народному хозяйству", хотя вспоминается лисп как скрипт язык в автокаде.

Ого. Смелое заявление.

И LISP не функциональный язык, он мутабельный и в нем можно нагородить объекты, если надо.

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

Исторически ФЯП существовал даже до ООП, но фактически именно ООП обеспечил качественный рывок, плюс существующий классический процедурный подход, без него никуда. Даже более редкий и экзотический Форт и то оставил более заметный след в истории, чем ФП.

Можно. Но зачем?

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

У меня одного сложилось впечатление что автор пытается пере изобрести Extension Methods из C#? Ну либо старый подход используемый в Zortech C++ (С++ транслировалось в чистый С, т.е. структуры с указателями функций)?

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

Ps. По моему мнению весь текст похож на результат ChatGPT.
Pps. А еще полиморфизм в С++ можно реализовать, через шаблоны а не виртуальные функции. Т.е. любой механизм можно использовать не так как задумывал автор.

Лучшая парадигма - та, которая работает для конкретной задачи. В ооп-like подходе банально удобнее структурировать данные и делать модели (ну да, ну да, не в 100% случаев, но это ж само собой разумеется; но все же, в большинстве).

Так что хз по поводу все этих споров.

Talk is cheap, show me the code (c).

Перечисли нам пару десятков продуктов enterprise-level написанных в ПП (ФП в твоём понимании).

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

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

Если бы, то что вы называете ФП, имело бы такое неоспоримое преимущество перед ООП, как вы пытаетесь показать в данной статье, неужели вы думаете, что многие годы существования данных парадигм, бизнес бы не пришёл к повсеместному использованию более выгодной в разработке парадигмы, по чисто экономическим соображениям?

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

То что Вы дали такое определение:

  • Функциональное программирование (ФП) — это программирование используя структуры и функции. Не путать с функциональным (математическим) стилем.

Ещё не делает его корректным. В профессиональной области это называется именно парадигмой Процедурного Программирования, а не ФП. То, что Вы называется математическим стилем, в профессиональной среде называется парадигмой Функционального Программирования.

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

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

Вы просто, не понимаете, 90% разработчиков тупые и не понимают что используют, профессоры CS тоже 90% тупые и не понимают что пишут.

Мне вот интересно, а те кто выезжает на встречку тоже ведь наверно думают что вокруг все идиоты и едут не в ту сторону.

Один Вы тут Д'артаньян, сейчас всех уму разуму научите! /s

Профессиональной среде рукожопов, выдумавших Java и C#.

Повторяю, вы уже отстали от жизни, самые популярные библиотеки для enterprise - react и redux, и пишутся именно в стиле что я описал.

Backend-сервера, кэш-серверы, субд, ОС и системные сервисы тоже на react/redux все пишутся? Или может не будем обобщать из частного?

Бэкенд пишется на Go и NodeJS с его express, самое популярное ядро ОС - на С. Вы все динозавры, сидящие на своих древних, уродливых и непродуманных ООП языках.

Несомненно есть реализации и на Go и на NodeJS, вопрос какова доля?

Вы ещё писали про самые популярные библиотеки для enterprise, написанные на react/redux. Я сомневаюсь, что такие библиотеки перевешивают долю по реализации на других языках/фреймворках.

Вы все динозавры, сидящие на своих древних, уродливых и непродуманных ООП языках.

Как конструктивно!

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

Автор статьи не понял, что Habr - не место для дискусскии.

Как же в таком случае он мог разобратья в ООП?

Автор, у вас в примерах кода есть комментарий // ФП, но вы пишите именно в процедурном стиле. Вот если бы вы написали примеры используя именно ФП - там были бы уже другие вопросики. Ну и вопросики по перформансу сразу бы возникли, так как не в чисто ФП языках нет нужных оптимизаций, типа хвостовых рекурсий. Да и в целом - ФП проигрывает в производительности императивному подходу (а тут уж извините, иногда, производительность - действительно нужна).

Вот вы топите за структуры и функции как в ГО или Раст. А теперь подумайте, почему сейчас Питон (как фронтенд к оптимизированному беку) - король горы во всяких научных вычислениях: правильно - потому что он ООП язык, который поддерживает перегрузку операторов, что позволяет удобно записывать математические нотации в коде. Это вам как пример удачного применения ООП. Да и в целом, ООП хорошо применять, когда ты знаешь предметную область и можешь декомпозировать её на сущности. А вообще ООП хорошо подходит для использования в программировании интерфейсов, как раз там оно хорошо себя проявляет, где есть чёткая иерархия наследования компонентов и где нужен полиморфизм. Ну и про производительность не забываем. Хороший пример Реакт - да, интерфейсы пишутся декларативно, то самое ФП, но декларативность привносит оверхед на дополнительные вычисления различий дерева для перерисовки интерфейса. И это, в целом, повсеместно, при использовании ФП, что порождает повышенные требования к квалификации программиста и его математической подготовке.

Вообще, как сказал один умный разраб Питона, который тоже угарает по ФП - на языке нужно писать так, как на нём принято: хочешь ФП - бери чисто ФП язык. Можно взять тот же Closure, чтобы использовать наработки из джавы.

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

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

Исключительно потому, что Google ему профинансировала мощную маркетинговую кампанию и поддержку в целом в конце нулевых. Тот же Ruby гораздо более ООП, чем Python. Ни одной объективной технической причины в популярности Python вы не найдёте. Одни следствия того, что кому-то в Google он субъективно понравился или как-то иначе договорились.

Объективно, это динамический язык с простым синтаксисом и простой семантикой. Бейсик наших дней. При этом он ещё имеет огромный репозиторий готовых библиотек. Поэтому технические причины его популярности вполне налицо. Можно быстро писать программы, не упарываясь эзотерическими знаниями, в том числе и принципами ООП.

это динамический язык с простым синтаксисом и простой семантикой

Ну, давайте сравним c Ruby и PHP. Все 3 с динамической типизацией.

У Ruby наиболее читаемый синтаксис. У PHP - C-подобный синтаксис, который многим знаком с универа. У Python - самый непривычный синтаксис, чувствительный к уровню отступов.

По семантике самый простой Ruby - есть только объекты и методы. В PHP и Python у вас есть ещё и процедуры, что повышает когнитивную нагрузку, напр. len(str), а не типичный для ООП str.length

При этом он ещё имеет огромный репозиторий готовых библиотек.

Это следствие популярности, а не причина. Вы ставите телегу впереди лошади.

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

Вадим.помой(посуду);

Звучит неплохо, но бывает же, что действия производятся неустановленными лицами или сами по себе. Это что касается действий. А вдобавок не вся семантика является операционной, на чём стоят декларативные языки.

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

Старая цитата

xxx: программный этюд "лопата":
xxx:
xxx: пользователь я = вселенная.найти( планеты.земля ).дураки.сортировать(по_убыванию)[0];
xxx: склад инструменты = вселенная.склады.геопоиск( критерии.ближайший(я) );
xxx: инструмент лопата = инструменты.подходящий( действия.копание );
xxx: лопата.субъект = я;
xxx: копаемое земля = лопата.объекты( действия.копание ) как копаемое;
xxx: // и вот теперь наконец-то
xxx: земля.копайся();

Эта дискуссия нас никуда не приведёт. Загуглите какие-нибудь туториалы по Ruby для начинающих, и сами поймёте, что Python тут ничем не проще.

Возможно, ещё повлияла простота Python. У Ruby всё же повыше порог входа с его метапрограммированием.

Можно всю жизнь писать на руби и так никогда и не узнать про метапрограммирование, в принципе.

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

Люди, пишущие на рельсах, не нуждаются в понимании, как там ActiveRecord под капотом разруливает все вот эти user.posts.

Я в свое время портировал эликсировские протоколы в руби, там одно сплошное метапрограммирование, но команда потом просто использовала DSL и забот не знала.

Не сходится, Ruby долгое время был популярнее Python. А PHP так был популярнее их обоих.

Это естественные погрешности на начальном этапе, но у этих языков разная целевая аудитория. Ruby и особенно PHP применяются в основном в вебе.

Вы опять телегу с лошадью местами путаете.

С таким же успехом я могу сказать, что Python применяется в основном для скриптов администрирования под Linux. Он, в принципе, был создан именно для этих задач. И на момент 2005-го года буду абсолютно прав, потому что он тогда больше нигде и не применялся. То, что он сейчас ещё где-то применяется - это вообще не заслуга Python как языка. Это тупо следствие вливания в его продвижение кучи бабла.

Просьба автору статьи.

Много букаф это конечно замечательно как и превознесение ФП как богоизбранного подхода и TS как языка всех языков и почти совершенства. Впрочем невпервой. Я уже много раз читал такие статьи. Читаешь, смотришь красивые hello worldы. Но вот то ли мне не везло, то ли что другое, каждый раз когда уже почти поверил что вот она серебряная пуля ФП, в интернетах или по работе попадается реальный проект написанный в стиле ФП и начинала идти кровь из глаз, приходилось созерцать тот адовый нечитаемый ад который там написан. Приходят новые разработчики которые вообще не могут ничего понять из этой божественной писанины. Как итог приходиться переписывать на богомерзкий ООП подход. Разумеется все морально очень страдают, но вот прочитать могут.

Это я все к чему. У меня большая просьба к автору - просто выложите или покажите где посмотреть большой боевой проект, над которым какое то время работало приличное количество людей, написанный в описанном Вами стиле. Мы все очень хотим посмотреть на код который лишен недостатков и превосходит ООПшный на десятилетия, нам всем хочется наконец то стать крутыми инженерами. Буду очень признателен.

Прошлый проект - 200 000 строк кода на TypeScript только своего, не считая библиотек. Вот только он коммерческий и приватный, выложить не могу.

И да, на каком нибудь С++ или Java это было бы x20 строк кода.

А так - гляньте React какой нибудь.

Ну а на $mol было бы не более 10 000 строк кода. Измерять размер проекта объёмом копипасты - такое себе.

Прошлый проект - 200 000 строк кода на TypeScript только своего, не считая библиотек. Вот только он коммерческий и приватный, выложить не могу.

Каждый раз одно и тоже. Громогласные возгласы что одно превосходит другое в 10 или 20 раз, что есть меч-кладенец которые решит все мои проблемы и как обычно кроме красивых слов никто не может ничего показать это на практике. Какой-то заговор не иначе.

И да, на каком нибудь С++ или Java это было бы x20 строк кода.

"Если бы у бабушки были C++ - она была бы дедушкой"(с)

И да, на каком нибудь С++ или Java это было бы x20 строк кода.

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

ООП на концептуальном уровне это всего два положения:

  • все моделируемые сущности суть объекты

  • объекты взаимодействуют пересылкой сообщений

На логическом уровне реализация ООП отличается от других языков только наследованием реализации (извините за тавтологию), остальное в том или ином виде имеется и в других языках.

Создание первых ООП-языков - середина 1960-х годов (Симула-67 - "Алгол с классами"), как следует из названия, они предназначались для имитационного моделирования (объекты активны), а не для пассивных сущностей вроде счетов и накладных. Второе удачное применение - целиком искусственные миры, вроде элементов графического интерфейса пользователя.

Основные критерии эффективности подходов:

  • степень связности компонентов (модулей)

  • степень повторного использования кода

По этим пунктам ООП показывает хороший результат, если не впадать в фанатизм "чистой архитектуры" и трезво оценивать метрики, когда объемы превышают хотя бы 100К строк.

Это просто объектное. Объектно-ориентированное это значит не "ориентированное на использование объектов", как могло бы показаться, а значит что объекты ориентированы относительно друг друга, что и означает наследование, но не обязательно реализации, а поведения. Поведение задается абстрактным классом либо интерфейсом.

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

Не реализацией а декларацией.

  1. Методы не проигрывают функциям, потому что они делают одно и то же. ЕДинственное отличие - они принадлежат конкретным объектам. Если этот метод у тебя применим к разным ситуациям (разным объектам) - он не должен быть методом конкретного класса, а быть, например в отдельном классе или просто быть функцией вне классов.

  2. Наследование - смысл наследования в создании общих данных и методов между разными классами.

type
  TBaseUser = class
  private
    FName: string;
    FSurName: string;
    FId: string;
    procedure SetId(const Value: string);
    procedure SetName(const Value: string);
    procedure SetSurName(const Value: string);
  public
    property Id: string read FId write SetId;
    property Name: string read FName write SetName;
    property SurName: string read FSurName write SetSurName;
    function GetDisplayName: string; virtual;
  end;

  TUser = class(TBaseUser)
  private
    FAddress: string;
    FFriends: TArray<TBaseUser>;
    procedure SetAddress(const Value: string);
    function GetHasFriends: Boolean;
    procedure SetFriends(const Value: TArray<TBaseUser>);
  public
    property Address: string read FAddress write SetAddress;
    property Friends: TArray<TBaseUser> read FFriends write SetFriends;
    property HasFriends: Boolean read GetHasFriends;
  end;

  TNPC = class(TBaseUser);

Где проблема? Хочется более гибко? Используй интерфейсы.

  INameable = interface
    function GetName: string;
    function GetSurName: string;
    procedure SetName(const Value: string);
    procedure SetSurName(const Value: string);
    property Name: string read GetName write SetName;
    property SurName: string read GetSurName write SetSurName;
    function GetDisplayName: string;
  end;

  IFriendable = interface
    function GetAddress: string;
    procedure SetAddress(const Value: string);
    function GetFriends: TArray<TBaseUser>;
    procedure SetFriends(const Value: TArray<TBaseUser>);
    function GetHasFriends: Boolean;
    property HasFriends: Boolean read GetHasFriends;
    property Address: string read GetAddress write SetAddress;
    property Friends: TArray<TBaseUser> read GetFriends write SetFriends;
  end;

Эти интерфейсы ты можешь навесить на любой вообще класс и можешь его запрашивать у любых объектов.

Вся суть ООП - в структурировании кода, в удобной поддержке его в будущем. Да, кода больше, но и архитектура продумывается более детально.

Через какое-то время, вот это type Npc = Pick<User, "id" | "name" | "surname">, превратится в ужас, потому что ты будешь навешивать, расширять и менять класс под новые нужды. В итоге, создашь новый. И у тебя будет всюду подобная лапша. Как и на ФП в целом, когда структур и методов для них будет масса и будет не понятно, что и в какой момент ты можешь использовать, а что - нет.

3. Про полиморфизм, внезапно, проблем у ООП нет, но вы это уносите в минусы, потому что в ФП полиморфизм тоже есть (и имеет те же проблемы).

4. Инкапсуляция - то же самое. Только вот в ФП - это спагетти-код, просто сплошняком, без какой либо структуры. Это просто не читаемо в будущем. Класс и его описание просто напросто проще читать, особенно, если описание и реализация класса - отдельны.

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

Посмотрите на примеры кода выше. Взглянув один раз на описание класса можно всё сразу понять. Что доступно, что - нет, какие типы возвращаются, какие параметры принимаются. Не нужно никуда листать или надеяться на подсказки редактора кода.

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

Товарищ демагог, так как переиспользовать из вашего класса GetDisplayName для собаки, наследуемой от Animal?

Собака - пользователь? Или может NPC? Когда объект конкретного класса используется наравне с другими (т.е. имеет что-то общее), он и наследуется от чего-то общего.

"Супер функция", которая принимает всё что угодно - это говнокод. Если вы намерены в ФП создать такой метод - то у вас крайне всё плохо с логикой и архитектурой.

В данном случае, Animal должен наследовать TBaseUser.

Ещё раз, чтоб вы поняли: GetDisplayName выводит имя. Выводит имя объектов имеющих общую характеристику - имя. Это не значит, что только одна функция должна все существующие объекты с полем Name: string принимать и выводить. Всегда объекты, к которым применяется одна функция - имеют общего предка. В этом и смысл.

Всегда объекты, к которым применяется одна функция - имеют общего предка. В этом и смысл.

Это так в ООП, но никакого смысла в этом нет.

Ключевое слово: конвергентная эволюция.

Смысл - обрабатывать разные объекты, разных классов, общими правилами (родительским классом или интерфейсом)

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

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

Я ж не зря написал про конвергентную эволюцию. Вам придётся отдельно реализовывать плавание для акулы, а отдельно для дельфина, и так везде. Хотя физика плавания одинакова.

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

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

Поэтому чуть что - от всей иерархии классов остаётся void*

Жду появления акторной модели в дискуссии, как единственно верного решения. Сообщения, посылаемые объектам. Алан Кай, привет.

Ну или duck typing, как ее вырожденный случай в мире ООП.

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

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

Нет, это не декорация. Пример:

  IAdder = interface
    function Add(I, J: Integer): Integer;
  end;
 

  TAdder = class(TAggregatedObject, IAdder)
    function Add(I, J: Integer): Integer;
  end;
 

  TPasClass = class(TInterfacedObject, IAdder)
    FAdder: TAdder;
  public
    destructor Destroy; override;
    property Adder: TAdder read FAdder write FAdder implements IAdder;
  end;

TPasClass поддерживает интерфейс IAdder, но метода Add в нем нет, этот метод проксируется из объекта Adder. Вдобавок к этому, можно переопределить методы для делегированного класса:

  IAdder = interface
    function Add(I, J: Integer): Integer;
    function Sub(I, J: Integer): Integer;    
  end;
 

  TAdder = class(TAggregatedObject, IAdder)
    function Add(I, J: Integer): Integer;
    function Sub(I, J: Integer): Integer;
  end;
 

  TPasClass = class(TInterfacedObject, IAdder)
    FAdder: TAdder;
    function MySub(I, J: Integer): Integer;
  public
    destructor Destroy; override;
    property Adder: TAdder read FAdder write FAdder implements IAdder;
    function IAdder.Sub = MySub; //<- перекрытие
  end;

При этом, работа с TPasClass, через интерфейс IAdder будет происходить как обычно.

https://docwiki.embarcadero.com/RADStudio/Sydney/en/Implementing_Interfaces

Этот метод позволяет добавлять возможность реализации интерфейса без реализации методов, а используя существующую реализацию.

Вы здесь, как я понимаю, объявили свой собственный класс TPasClass, унаследованный от стандартного класса TInterfacedObject. Это позволит вашим собственным экземплярам типа TPasClass пользоваться интерфейсом IAdder, но никак не решит вопроса с экземплярами типа TInterfacedObject, созданными другими людьми.

В то время как при параметрическом полиморфизме вы можете свободно оперировать объектами любых типов и происхождения.

Нет, TInterfacedObject - это вообще базовый класс, как TObject. Он ничего не реализует кроме подсчета ссылок. Любой, кто создаст свой класс может использовать интерфейс IAdder и готовую реализацию TAdder в своих классах, делегировав таким же способом.
При этом, они могут как инициировать свой собственный объект класса TAdder, так и взять где-то готовый и назначить через свойство.

Наличие тут TInterfacedObject - это заморочки Delphi. В Delphi нет сборщика мусора, однако, если использовать интерфейсы, то можно использовать ARC (автоматически подсчет ссылок), который реализован в этом базовом классе. Т.е. как правило все классы, которые должны будут иметь интерфейсы, наследуются именно от этого класса. Можно сказать, что это такой синтаксис для создания классов с интерфейсами.

Тогда в вашем примере вообще нет содержательного наследования, и непонятно, что он должен иллюстрировать и какое отношение имеет к словам про "общего предка".

Речь шла о переиспользовании. Когда есть несколько разных классов объектов, которые должны уметь "плавать". Будем иметь два разных класса, двух разных животных, добавляем классам интерфейс ISwim и используем делегирование на класс TSwim, реализующий плавание. При этом, код плавания не дублируется и можем влиять на то, как объект может плавать.

Это всё хорошо только до тех пор, пока вы заранее знали о необходимости реализовывать интерфейс в момент реализации классов этих животных.

У меня в статьях на хабре есть пример переопредления печати байтовых массивов в системных сообщениях на Scheme. Совершенно очевидно, что человек, который писал вывод системных сообщений, не имел никакого представления о том, что есть на свете такой vadimr, который хочет видеть визуальное представление определённого системного типа по-другому, чем это предусмотрено в системной библиотеке. Тем не менее, форматировщик с параметрическим полиморфизмом позволяет без труда решать эту задачу.

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

Именно это, как я понимаю, имел в виду Линус, говоря о ненадёжности STL.

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

Ну так напишите аналогичный код в любимой вами системе с ООП подходом.

Я не понимаю при чем тут вообще ООП (речь о вашем примере). Это просто формат вывода данных. В Delphi стандартный Writeln не выведет ничего кроме примитивных типов. Это значит, что будет использоваться другой методы вывода, в котором, я могу вывести переданные данные как мне хочется. И ещё раз, ООП тут не причем вообще.

В Delphi стандартный Writeln не выведет ничего кроме примитивных типов

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

Ну хотя бы значения типа boolean попробуйте заменить на "истина" и "ложь".

Это все равно будет отдельная функция для ввода. Никто не пользуется напрямую функцией writeln внутри своего кода. Используется либо класс для вывода данных, либо просто свой метод вывода. В котором, я могу как угодно вывести любой тип без переопределения чего-либо.

Так делается потому что:

  • Не все типы можно вывести

  • Контекста вывода в консоль может не быть

  • Требуется часто вывод в более специфическом формате

  • Можно изменить контекст вывода (например, если это, лог в файл)

Да дело-то не именно в выводе. А в том, что вы в парадигме ООП вообще не можете изменить поведение кода, который не сами написали. Можете только отнаследоваться от него в свой код и там менять, либо править чужие исходники (из чего возник open source).

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

Log4J и типа того справляется же как-то.

Не, ну так-то и смоллток в природе существует. Говоря выше о парадигме ООП, я, понятное дело, имею в виду вульгарное рабоче-крестьянское ООП, как, например, его излагают деятели типа Мартина.

Log4J на рабоче-крестьянском ООП и написан :)

А еще всякие хадупы и спарки.

Я не в восторге от парадигмы в большинстве случаев (а Мартину вообще надо бы запретить писать на законодательном уровне), но ваш экстремизм не разделяю тоже. Любую задачу можно решить в любой парадигме, а конкретно полиморфизм — одинаково легко.

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

Это одна из базовых вещей в ООП — возможность запретить что-то менять в классе.

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

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

Запреты — это базовая вещь для того, чтобы говнокодеры не попортили код до безобразия. К ООП никакого отношения они не имеют.

Я вам даю честное слово, что вы не можете в Паскале перевести false и true на русский язык не потому, что западные империалисты специально хотели это вам запретить.

Нет в Паскале такого понятия вообще. Вывод всегда на совести разработчика. Стандартный вывод - это для начинающих. И вывод значения boolean нельзя вывести через writeln как хочется, поэтому что выводятся сырые данные из памяти. У примитивных типов нет никаких методов ВООБЩЕ. Нет никакого ToString. Нет методов! Выводится данные из памяти. Всегда выводятся данные через свои методы вывода данных. И вариант вывода очевидно полностью под управлением пользователя.

И вывод значения boolean нельзя вывести через writeln как хочется, поэтому что выводятся сырые данные из памяти

Вы думаете, что в памяти находится слово "true"?

У примитивных типов нет никаких методов ВООБЩЕ. 

Это проблема даже не ООП в целом, а конкретно языков вроде Паскаля. Просто семантический косяк, вызванный неэффективностью обращения к методам через VMT.

В памяти находится 0 или 1 (или -1 и 0). Boolean - это 1 байт (именно байт, в Паскале).

Паскаль - компилируемый язык. Примитивные типы там - НЕ КЛАССЫ и ОБЪЕКТЫ, а просто ссылки на данные в памяти. У них нет никакой VMT и быть вообще не может.

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

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

Что значит «существовать не может»? Даже не вчитываясь в комментарии выше, на основе банальной логики, очевидно, что:

  • проблема определяется не языком, а пользователем (разработчиком), и оратор выше вам говорит: вот есть такая задача, ее надо решить

  • вы говорите в ответ, что проблемы не существует

Так не бывает (вне политики, конечно, там-то бывает и не такое).

Существовать не может, потому что примитивные типы не имеют методов в принципе и следовательно их не надо переопределять.

Я в курсе, как устроены примитивные типы. Тем не менее, проблема существует: пользователю может потребоваться вывести да/ нет вместо булевых значений.

Параметрический полиморфизм её успешно решает. Наследование — нет.

Это решается тем, что изменяется метод вывода конкретного типа. Этот метод всегда пользовательский. Не надо ничего городить и оверхедить. Тут нет никакого вообще наследования. И ООП тут вообще никаким боком

примитивные типы не имеют методов в принципе

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

Не видите ли вы противоречия в двух высказанных вами один за другим тезисах?

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

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

А разве множественное наследование не лучше?

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

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

У прикладного разработчика проблемы нет. А у разработчика языка есть куча куда более серьёзных проблем, чем принятие решения в каком порядке резолвить методы.

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

Множественное наследование даёт очевидную проблему с ромбом

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

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

Что вы имеете в виду?

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

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

Хотя в смоллтоковском ООП, (не использующем наследования)

Как это Smalltalk без наследования? Там всё на этом построено!

Да, плохо написал. Имел в виду, что наследование не требуется в смоллтоке для полиморфизма.

В ооп тоже не требуется (ну если мы подразумеваем наследование реализации)

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

И это чертовски правильно, потому что применять функцию на данных, ей не подходящих - это бессмысленно. Garbage In - Garbage Out. А в контексте разработки ПО - даже опасно, потому как может вызвать ошибки.

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

Вопрос тут в том, кто определяет, какие данные подходят функции.

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

Может показаться, что некоторые явные ограничения избыточны.
Например, зачем использовать интерфейсы и типы, если функцию можно применить на любом списке или любом объекте с полем Х.
Но на самом деле не все такие операции имеют смысл.
Например операция "Задай имя и фамилию", меняет лишь поле "Имя" структуры "пользователь" и теоретически может работать с любым объектом, имеющим имя. Но проблема в том, что логически эта операция имеет смысл только с пользователям и попытка применить его на "заказ" (тоже имеющий имя) - бессмысленная и ошибочная операция. Позволять выполнить эту функцию с объектом "заказ" значит позволить совершить случайную ошибку, которую можно избежать.

Очевидно, что функция "задать ФИО" не должна применяться к заказу. Но функция "отсортировать по именам" или "перевести имя на китайский язык" вполне может применяться и к людям, и к заказам. Всё зависит тут от самой функции.

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

Я именно это и утверждаю. Но это параметрический полиморфизм.

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

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

«Есть поле — делай» — это же row polymorphism, и там всё вполне строго (очень хороший папир).

как переиспользовать из вашего класса GetDisplayName для собаки, наследуемой от Animal?

Никак. У пользователя 2 поля firstName и lastName, у собаки 1 поле name или nickName.

У меня вот отчество есть.

Вообще, представление имени человека в виде полей firstName и lastName – это один из самых вредоносных антипаттернов, если задуматься.

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

Вообще, представление имени человека в виде полей firstName и lastName – это один из самых вредоносных антипаттернов, если задуматься.

Почему? Если у вас официальная бумажная форма, в которой есть отдельные поля "Фамилия, Имя, Отчество (если есть)" или интеграция с другой системой, например для покупки билетов, где поля "Имя" и "Фамилия" отдельными полями. Какие у вас варианты, кроме как и в вашей системе хранить все эти поля именно так, по отдельности?

Варианты? Разметка.

Потому что у американца эти поля в официальной бумажной форме другие, а у китайца третьи.

Это почти такая же дичь, как кодировка ASCII.

А как потом выбрать всех Ивановых?

Можно использовать джейсон для хранения персональных данных, тут и с выборкой всё более-менее в порядке, но опять же: зависит от задачи. Нет никакой серебряной пули. Если мы проектируем доступ к РЖД, то китайцам и американцам придется подвинуться, и полям first и last — быть.

Если речь про универсальное ну пусть фитнес-приложение — там вообще никаких полей не должно быть, кроме «О себе».

В общем, бизнес определяет обязательные поля, и они должны быть прибиты гвоздями. А остальное — хоть плейнтекстом с разметкой, хоть свободным джейсоном, хоть прилинкованным документом в NoSQL.

А в чём практический смысл выбрать всех Ивановых?

А если ещё задуматься, что "всех Ивановых" подразумевает и "Иванов" и "Иванова", но второе – только для женщин...

в чём практический смысл выбрать всех Ивановых?

Опечатка в имени, нужно найти запись с оплатой.

Не нужно цепляться к конкретному примеру.

Это важное цепляние к конкретному примеру. На практике там всё равно нужно будет что-нибудь типа ФИО like "Иван%" (а потом окажется "Ван И").

А что вы имеет ввиду по "разметкой"?

Это почти такая же дичь, как кодировка ASCII.

Есть бизнес-область, типа тех же авиа-билетов международных. Не важно, какие в стране традиции - в документе под названием "билет" будет отдельно Фамилия, отдельно Имя (всё остальное?), которые потом будут сверять с загран-паспортом или иным документом. И на любом сайте для получения инфы о билете нужно будет ввести две вещи: номер билета/брони + Фамилию.

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

Я имею в виду текстовую строку name (возможно, с каким-то специальным индексом, в котором, например, будет указано, что по мнению источника здесь фамилия находится на первой позиции, а имя на двенадцатой) и функции getFirstName(name), getLastName(name), getPatronym(name), и что там ещё нужно для конкретной цели.

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

Мне один известный маркетплейс примерно в таких рассуждениях посылает посылки, печатая на упаковке моё ФИО в формате "Петрович Пупкин Василий". Видать, дёрнул из какой-то другой своей программы, перепутав назначение полей. И так и понеслось. Хотя я, понятное дело, совершенно уверен, что нигде так не представлялся.

Варианты? Разметка.

А выводить-то ее как, прямо так с тегами в интерфейсе пользователя Ваше имя: <Name>Abc</Name><Surname>Def</Surname>? Или всё-таки сначала надо извлечь данные в виде структуры? А выводить их потом как?

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

И я замечу, что вы не ответили на заданный выше вопрос.

Ответил, "никак". У человека и собаки разный набор полей, поэтому будут 2 функции "GetDisplayName(User user)" и "GetDisplayName(Dog dog)" с разным кодом. Классы как раз и нужны, чтобы указывать в типах аргументов. Эти функции должны быть в слое представления, но если хочется, то можно и в модель поместить. Тогда в каждом классе все равно будет своя функция (вычисляемое свойство). Вызывающий код при этом не знает детали реализации - хранится ли оно отдельно или вычисляется по другим полям.

Всё логично, только никакого ООП в этом нет. Это просто параметрический полиморфизм, а через классы его делать, через интерфейсы модулей или через case – неважно.

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

А у нас есть на работе коллега, у него нет фамилии и отчества, только двойное имя.

Вы придумываете какие-то умственные выверты, которые может "решать" ФП и не может, или с трудом - ООП, и при этом пытаетесь доказать, что язык с ООП - сложный и непонятный. Я плохо знаю ФП, и честно говоря, ни разу в своей карьере не встречал случая, когда вот прям необходимо было использовать функциональное программирование. Даже использование лямбд, когда НЕОБХОДИМО было их использовать, был всего один случай. Остальная рутина, когда постоянно используются лямбды, стримы и прочие опционалы - совершенно не являлось необходимостью, просто дань моде и признак крутизны. Да и корпоративная повестка, чо уж.

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

ТС настолько эксперт в программировании, что придумал свою терминологию: он функциональным программированием называет не функциональное программирование, а процедурное.

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

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

const state = {...};

const registerTool = <T,>(key: string, tool: Tool<T>) => {
  state.tools[key] = { tool, state: tool.createInitialState() }
}

const setActiveToolKey = (key: string) => {
  if (!state.tools[key]) return
  state.activeTool = key
}

Не первый раз встречаю людей с ФП головного мозга. Они часто сравнивают свои подходы с любыми другими ВООБЩЕ НЕ УЧИТЫВАЯ затраты на аллокацию и очистку памяти. Еще, у них часто проблемы с выделением абстракций (они решают задачи слишком буквально, без оглядки на такие мелочи как архитектура). Еще они считают, что гибридный подход - это, автоматически, плохой код, что неверно с точностью до наоборот. Пожалуйста, пишите код как хотите, но держитесь от меня подальше со своей религией.

А еще хуже люди, которые думаю, что они адепты ФП, а на самом деле понимают под ним ПП (процедурное т.е.)

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

Сериалазация и копирование:

import com.fasterxml.jackson.databind.ObjectMapper; // Для работы с JSON
import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) throws Exception {
        // Создаем объект user
        List<String> friendIds = new ArrayList<>();
        friendIds.add("2");
        User user = new User("1", "Вася", "Петров", friendIds);

        // Поверхностное копирование с изменением и добавлением полей
        User updatedUser = new User(user.getId(), "Василий", user.getLastName(), user.getFriendIds());
        updatedUser.setMiddleName("Иванович"); // Добавляем опциональное поле

        // Глубокое копирование (используем сериализацию/десериализацию)
        ObjectMapper objectMapper = new ObjectMapper();
        String userJson = objectMapper.writeValueAsString(user); // Сериализация в JSON
        User clonedUser = objectMapper.readValue(userJson, User.class); // Десериализация из JSON

        // Сериализация в JSON
        String userJsonString = objectMapper.writeValueAsString(user);

        // Десериализация из JSON
        User parsedUser = objectMapper.readValue(userJsonString, User.class);

        // Вывод результатов
        System.out.println("Original User: " + user);
        System.out.println("Updated User: " + updatedUser);
        System.out.println("Cloned User: " + clonedUser);
        System.out.println("User JSON: " + userJsonString);
        System.out.println("Parsed User: " + parsedUser);
    }
}

Чем ваш код лучше этого, что ФП дает в данном примере перед ООП (Сгенерировано DeepSeek)? Функционал отделен от данных (Spring - одно большое отделение).

Так вы не привели самое главное - класс User, или он из воздуха берется?

В моем примере весь код, и описан тип вместо класса:

type User = {
  id: string
  firstName: string
  lastName: string
  middleName?: string
  friendIds?: string[]
}

Также, у вас нужно перечислить все поля в конструктор, а если их 100?

Также у вас создается объект ObjectMapper. И много по мелочи.

Также, у вас нужно перечислить все поля в конструктор, а если их 100?

Ну а в TS как будто не нужно перечислять все поля, если создаёшь объект литералом.

Речь про поверхностное копирование.

Забыл еще момент - глубокое копирование почему то идет через костыль с сериализацей и десериализацей в JSON (facepalm).

Первым звоночком, заставившим задуматься об удачности концепции ООП стала травма детства задача с первого собеседования по ООП языку, который я на тот момент еще только начал учить — C#, и которая врезалась в память по сей день

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

том факте, что 99% людей идиоты имеют IQ<140

Так вот почему они используют react в связке с redux'ом, правда в этом случае граница в 140 сильно завышена, 80 это больше похоже на правду

Но и это были цветочки — далее могли следовать вопросы по паттерну синглтон

А что с ним не так? Синглтон - отличная вещь. В переводе например на язык JS и фронта это просто экземпляр глобального состояния, с который легко и приятно работать.

Во-первых, метод намертво приколочен к типу своего скрытого аргумента — this, которым является User. Он зависит не от интерфейса, а от конкретного класса

И что?) Для этого этот класс и создавался, и getDisplayName() касается именно юзера, поэтому он и там. Мы не используем getDisplayName для web socket коннекшена, или для парсинга http заголовков, или где угодно ещё. Мы используем getDisplayName() конкретно чтобы получить имя пользователя отформатированное. Поэтому супер правильно и логично, что у User есть этот метод. Пункт не засчитан.

Переиспользование с другими типами

Ну во первых переиспользовать и вставить в другой класс легко. Допустим тоже самое для животного)

getDisplayName() {
    return [this.petName, this.ownerFirstName, this.oswnerLastName]
      .filter(Boolean)
      .join(" ")
  }

Во вторых переиспользование так же можно реализовать и через композиции

class Test {
  flag1 = new WithFlag();
  flag2 = new WithFlag(true)
}

В третьих, через так называемую универсальную функцию

class User {
  getDisplayName() {
    return universalDisplayName(
      [this.petName, this.ownerFirstName, this.oswnerLastName]
    )
  }
}


Пункт не засчитан.

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

Вообще нет, см. примеры выше. Пункт не засчитан.

  • Необходимость дальнейшего использования классов: нельзя использовать метод без создания экземпляра этого класса или его потомка. Например, нельзя использовать для словаря с такими же полями.

Правда?) static в помощь) Пункт не засчитан.

class User {
  static hello(what: string) {
    console.log(`Hello ${what}`);
  }
}
// Никаких экземпляров не создает
User.hello("world"); // Hello world в консоли
  • Невозможность обработки ситуации, когда user null или undefined в самом методе.

Ну и дальше у вас такое

let user: User | null

user.getDisplayName() // Ошибка: null reference.

Естественно так и будет, потому что совершили сами ошибку грубую. Никакой ООП тут не причем. С таким же успехом:

let userFn: function | null

userFn() // Ошибка: userFn is not a function

По вашей же логике - ФП отстой, "Невозможность обработки ситуации, когда user null или undefined в самой функции"

В общем и тут пункт не засчитан.

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

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

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

Каким образом переопределение = выстрел в ногу?

Ваш пример, в котором все хорошо
Ваш пример, в котором все хорошо

Ну а как вам такое?) Всё ровно тоже самое:

class Parent {
    parentMethod() {
        console.log('parent method')
    }
}

class Child extends Parent {
    parentMethod() {
        // Вставляейте if или все что угодно
        super.parentMethod();
        console.log('child method')
    }
}

const child = new Child();
child.parentMethod();

// console output:
// parent method
// child method

Не засчитано.

Получается, что методы проигрывают функциям во всем

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

Чтобы переиспользовать что-либо из имеющегося класса

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

Итог: наследование добавляет множество проблем, но не решает ни одной. Мусор даже по меркам ООП.

Наследование просто не для всего и не всегда подходит. Так где оно не подходит и не к чему, там да - мусор. А там где к месту, там оно прекрасное и удобное.

Полиморфизм — способность функции обрабатывать данные разных типов

...

В ФП принято использовать параметрический (истинный) полиморфизм:

Вот опять ваш пример ФП, только с нормальным ООП. всё опять по сути 1 в 1 практически.

Скрытый текст
class Circle {
    type = 'circle';
    radius = 0;

    constructor(public radius: number) { }

    getArea() {
        return Math.PI * this.radius * this.radius;
    }
}

class Rectangle {
    type = 'rectangle';
    width = 0;
    height = 0;

    constructor(public width: number, public height: number) { }

    getArea() {
        return this.width * this.height;
    }
}

type Shape = Circle | Rectangle;

const logShapes = (shapes: Shape[]) => {
    shapes.forEach((shape) => console.log(`Площадь: ${shape.getArea()}`))
}
logShapes([
    new Circle(5),
    new Rectangle(4, 6),
])

// В библиотеке функцию можно сделать более гибкой, использовав обобщения и аргумент getArea (можно опциональный,
// с реализацией по умолчанию), которой вообще все равно, какой тип предоставляется.
const logShapes2 = <T, >(shapes: T[], getArea: (shape: T) => number) => {
    shapes.forEach((shape) => console.log(`Площадь: ${getArea(shape)}`))
}

logShapes2<Shape>(
    [
        new Circle(5),
        new Rectangle(4, 6),
        { type: 'triangle' }, // Ошибка компиляции: данный тип не поддерживается getArea.
    ],
    (shape) => shape.getArea(),
)

Вы специально пишете максимально неуклюжие примеры с ООП чтобы он выглядел более ужасным. А по факту, все более чем отлично.

Инкапсуляция

Ну и далее "аргументы" против

"Аргументы" ваши по этому пункту вообще кране слабые и притянуты за уши

увеличивает количество кода

Да? Ну тогда название функции getUserById() увеличивает кол-во кода, надо писать так: f1()

усложняет тестирование

Каким боком? :D

замедляет работу приложения

Да вы что? Вот прям везде и во всех языках всё проверяется в рантайме, а не во время компиляции? =))

Языки ООП чрезмерно усложнены избыточным синтаксисом

Бери и используй только то, что нужно, что удобно и что нравится. Профит.

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

Опять же бери и используй только то, что нужно, что удобно и что нравится. Профит.

Синтаксис языков ФП намного проще

Всё настолько идеально, что вы везде имитируете классы объектами. У которых так же есть свойства, у которых так же есть методы. Как это называется? Ли-ци-ме-ри-е.

Шаблоны (паттерны) проектирования

99.99% можно на помойку да, остальное и так совпадает с принципами здравого смысла.

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

Ясно, понятно))

Работа с массивами

// Правильно ли обновлять массив user таким образом?
// Что тут должен придумать неокрепший ум начинающего ООП-шника?
for (let user of users) {
  user.update()
}

И в чем тут проблема? Можно завернуть в функцию, можно завернуть в класс Users

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

Какие костыли?) если у вас апи позволяет за один запрос изменить множество user'ов. то какие ещё проблемы и костыли?

Это точно так же работает для классов.

Огромным преимуществом математического стиля ФП является поддержка конкурентности (concurrency) без дополнительных усилий и синхронизаций

Подумаешь расплата за это низкая производительность(аллокации на каждый чих, а это syscall'ы, а это медленно) и завышенное потребление оперативной памяти. Далеко не всем, как вам насрать на производительность, поэтому когда речь про реально конкурентную многопоточность - mutex наше всё. И ничего сверх человечного в этом нет

mutex.Lock();
// Прочитал/изменил данные которые много кто может читать/писать одновременно
mutex.Unlock();

Ничего сложного, тяу тяу тяу тяу.

И в реальной жизни никто и никогда так не пишет, даже самый отсталый

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

Ну во первых переиспользовать и вставить в другой класс легко. Допустим тоже самое для животного) ... далее просто копипаста

То есть первый способ копипаста и это хорошо?

можно реализовать и через композиции

То есть внутри собаки нужно создать User с ее полями и это хорошо? Проблема банана и макаки это круто?

В третьих, через так называемую универсальную функцию

То есть единственный способ нормально написать код это использовать функцию, то есть ФП? Так про это и есть статья, поздравляю, вы поняли ее суть.

А по факту, так мы и не узнали, как же переиспользовать метод класса в другом, не копипастя, не получая макаку с джунглями, не используя ФП.

Ничего сложного, тяу тяу тяу тяу.

Блокирующая синхронизация, привет фризы из главного потока, рейс кондишены и дедлоки. Хотя кому я объясняю..

Удобно, ответить только на те тезисы, на которые есть, что ответить (по мнению автора) и проигнорировать ответы на остальное (с примерами как и просили).

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

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

То есть единственный способ нормально написать код это использовать функцию, то есть ФП?

Ну а кто мне запретит использовать ФП и ООП вместе по мере необходимости?) И это не доказывает никакого превосходства. Просто для одного хорошо использовать один подход, а для другого – другой. И помимо Java и C# есть и другие языки в которых это вполне возможно.

А по поводу примера. Можно и не отдельной функцией. Можно было сделать общий интерфейс с функцией getDisplayName(), а в классах где это необходимо добавить его реализацию. Мне вот не хотелось бы чтобы этот метод использовал тип данных для которого это не предназначено во избежание дальнейших ошибок, особенно при расширении и изменении функциональности. Ну а переиспользование это конечно хорошо, но не когда ради него приходится писать огромную функцию переберающиую все возможные случаи переданных данных. Может у меня для разных данных должна быть разная логика их обработки, для каждых плодить отдельную функцию, которая к тому же проверяет что ей передали менно те данные с которыми она должна работать? И где тогда удобство переиспользования и почему это лучше вызова метода класса?

Блокирующая синхронизация, привет фризы из главного потока, рейс кондишены и дедлоки.

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

Удобно, ответить только на те тезисы, на которые есть, что ответить 

Есть что ответить на все, пусть сначала на первое ответит.

переиспользование это конечно хорошо, но

Так и знал - типичные отмазки про то что это может и не надо. Да нельзя никак метод класса переиспользовать, вот и все. Но можете дальше сопли жевать.

Например, в той же Java, есть volatile и потокобезопасные типы данных, которые прекрасно могут работать без излишней синхронизации

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

В шарпах этот ваш getDisplayName можно вообще хоть к чему прикрутить через экстеншены, было бы желание.

using System;
					
public class Program
{
	public static void Main()
	{
		Console.WriteLine(123.getDisplayName());
		Console.WriteLine(true.getDisplayName());
	}
}

internal static class IntDisplayNameExtensions
{
	internal static string getDisplayName<T>(this T value) where T : struct 
          => $"My name is {value}!";	
}

Output:

My name is 123!

My name is True!

Но вообще конкретно для вывода текстового отображения структуры/класса обычно используется переопределение ToString которое без участия разработчика дёрнется при использовании интерполяции строк, как в этом примере вызываются ToString у int и bool констант.

  1. Прикрутить их нужно тогда уж к интерфейсу, содержащему поля имен.

  2. В решении опять присутствуют статические методы. А это ФП - без него получается никак.

То есть первый способ копипаста и это хорошо?

1) Там я привёл ещё способы как пример.
2) Копипаст один из них, и да, это не всегда плохо, все зависит от конкретных целей и задач. Где-то лучше копи паст плюс добавленная/немного измененная логика, чем универсальная функция с кучей if'ов чтобы обработать разные сценарии применения.

То есть внутри собаки нужно создать User с ее полями и это хорошо?

Не нужно, а можно в том числе так, почему нет. Разработка не делится на черное и белое, между ними бесконечное число оттенков) Разработчик сам знает и сам решает, что и где создавать, с какими полями т п. И даже если там будут "лишние" поля, то это в худшую сторону не повлияет.

Проблема банана и макаки это круто?

Хз о чем вы)

То есть единственный способ нормально написать код это использовать функцию, то есть ФП? Так про это и есть статья, поздравляю, вы поняли ее суть.

Единственный способ нормально написать код - не быть упоротым и зацикленным на чем-то одном, а брать лучшее из всех подходов и комбинировать их.

А по факту, так мы и не узнали, как же переиспользовать метод класса в другом, не копипастя, не получая макаку с джунглями, не используя ФП.

Можно для этого использовать static метод в классе, если вы из крайности в крайность бросаетесь, где использование функций вместе с классами это преступление)

Блокирующая синхронизация, привет фризы из главного потока, рейс кондишены и дедлоки.

Не все задачи могут обходится без mutex'ов. Просто примите это. И ещё, mutex lock + unlock в том же Go съедают 15-20 нано секунд, я замерял, и mutex в разы быстрее чем те же каналы в Go. И да, используя каналы в Go так же можно словить deadlock.
По поводу фризов, с чего это они возникают в "главном потоке?" Они возникают только в тех, кто ждёт очередь к доступу на конкретных Mutex если он заблокирован.

Хотя кому я объясняю..

Вот вот, по вашему я ничего не смыслю что ли и знаю меньше вашего? =)

P.S. Я бы отвечал чаще, но из-за слитой кармы могу писать только 1 раз в сутки.

Не закрепить ли мне этот комментарий..

про реально конкурентную многопоточность - mutex наше всё

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

А вот с другим параллельным кодом, который выражается в композицию map и reduce, не только на ФП языках, но и на императивных проблем ровно ноль. Синтаксис в ФП проще? Ну и пофиг, эти особенности на порядок менее сложны, чем вообще сама декомпозиция в map/reduce парадигму.

Каким образом переопределение = выстрел в ногу?

А я вот чего не понял про переопределение функции.

Человек приводит пример с переопределением функции:

const getUserDisplayName = (user: ...) => {...}

const getAdminDisplayName = (admin: ...) => {
  if (...) {
    // В определенных случаяx переиспользуем getUserDisplayName.
    return getUserDisplayName(admin) 
  }

  // Какая то уникальная для admin логика отображения имени.
  return ...
}

А где функция та переопределялась? Вижу была создана новая функция, а переопределения уже готовой функции не вижу.

Я в классе могу так же написать - просто создать еще один метод, назвать его по другому, использовать внутри другой метод и сказать что это переопределение?

А вот теперь задачка автору - пусть попробует написать действительное переопределение функции, которая бы не поменяла свое название, но вела себя по разному в зависимости от объекта/интерфейса с которым она работает. И самое главное, что назновидность этих интерфейсов может быть например около 100)

Эта задачка решает копеечным наследованием. Хотелось бы посмотреть как это решить через одну функцию ;)

Вот кстати замечательный пример почему на этом нельзя писать большие проекты:

type Shape = Circle | Rectangle

const getArea = (shape: Shape): number => {
  // Убедитесь, что правило ESLint @typescript-eslint/switch-exhaustiveness-check, требующее исчерпывающие блоки switch, включено.
  // Данный код полностью типизирован и проверяется компилятором.
  switch (shape.type) {
    case "circle":
      return Math.PI * shape.radius * shape.radius
    case "rectangle":
      return shape.width * shape.height
  }
}

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

Еще вопрос, как писать большие библиотеки с закрытым кодом? Когда доступен API, но не реализация.

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

При таком подходе в куче методов должен быть такой case (GetPerimeter, GetMassa и прочее), это очень тяжело поддерживать

И с чего бы это тяжело поддерживать?

Можно написать функции для разных фигур в отдельных модулях - файлах или папках, и положить при желании в `Record<ShapeType, { getArea, getMassa, getPerimeter }>` и т.п.

Использовать `shapeByType[shape.type].getArea(shape)`, без классов и их ограничений.

При добавлении нового ShapeType компилятор подскажет какие функции нужно реализовать и куда добавить.

А если третьесторонний разработчик хочет написать динамически подгружаемый плагин с поддержкой новой геометрической фигуры?

Вот пример с добавлением инструмента в библиотеку, генерируемый на лету. Без проблем.

То есть мсье написал говнокод, и теперь пытается съехать с темы?

А если надо ещё 10 объектов добавить, что, все свитч кейсы в сотне мест переписывать?

Простейший же пример.
Кроме .GetArea() у shape может быть .GetCenter(), GetLineLength() и так далее.

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

Еще вопрос, как писать большие библиотеки с закрытым кодом? Когда доступен API, но не реализация.

Набор типов - это часть API, обычно. Другое дело, что тут нужно ещё потрудиться придумать кейс в котором type Shape = Circle | Rectangle - это достаточно.

Это ещё ТС не добавил фигуру, как композицию произвольного количества кругов и прямоугольников и остальных будущих фигур, и она тоже хочет уметь в getArea ()

Не судите только про ФП по данной статье. Я даже не знаю, чем надо было думать, чтобы взять TypeScript и Go в качестве примеров функциональных языков.

Секцию определений я смотрю вы пропустили.

То, что вы сами не разобрались с определениями и пытаетесь свои выдумать - это просто выглядит глупо.

"программирование используя структуры и функции" - это процедурное программирование, а не функциональное.

ФП основано на лямбда-исчислении, никакого другого ФП не может существовать. Чтобы у вас было ФП, необходимо иметь first-class functions, higher order functions и иммутабельность данных. Если чего-то из этого нет, а что-то есть, то перед вами язык с элементами ФП, но не функциональный.

Я в примерах возвращаю динамически созданные функции с изменяемым замыканием. Это по вашему процедура? И кто тогда не разобрался?

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

Вот более подробное: "Procedural programming is a programming paradigm that involves implementing the behavior of a computer program as procedures (a.k.a. functions, subroutines) that call each other. Procedural programming is about dividing the program implementation into variables, data structures, and subroutines (a.k.a. functions, procedures)"

К ФП ваше определение ровным счётом никакого отношения не имеет.

Еще раз - вы утверждаете что мои примеры где я из других функций "возвращаю динамически созданные функции с изменяемым замыканием" - это процедурное программирование? Ответить можете на прямой вопрос?

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

Я добавил определение процедурного стиля, и оно сильно более ограничено чем мое определение ФП.

От прямого вопроса ушли, слив засчитан.

Я добавил определение процедурного стиля
Процедурный стиль - стиль ФП, в котором

Вы эти грибы больше не ешьте, а то галлюционируете похлеще самой убогой нейросетки xD

Процедурный стиль к ФП вообще никакого отношения не имеет.

Так в каком стиле мои примеры кода? ПП вы назвать отказываетесь, ФП тоже. И уж явно не ООП, как тогда? Вы сливаетесь с вопроса похлеще самого убогого школьника.

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

А в этой ветке мы обсуждаем определения, а не код. Перечитайте её сначала.

То есть статья про "код с элементами ФП"? Парадигму, которой предлагается писать вообще весь код, и в которой я пишу уже давно сам, и которая считается мной около-идеальной, нет отдельного названия?

Ну ок, ваше мнение, но не мое.

Да, всё так. Вас тянет к ФП, но вы не решаетесь попробовать функциональный язык, а довольствуетесь отдельными элементами. Например, вы не хотите использовать полиморфизм в стиле ООП, который в TypeScript является основным вариантом полиморфизма, и довольствуетесь switch/case.

Когда вам справедливо указывают, что такой вариант не расширяем (если вы пользуетесь какой-то либой, а её разработчик не предусмотрел case под ваш случай), вы просто обижаетесь и пытаетесь что-то доказывать.

В функциональных языках просто есть нормальные решения для полиморфизма на этот случай. Например, протоколы в Elixir: https://hexdocs.pm/elixir/protocols.html

В TypeScript ничего подобного нет и не будет, потому что его основная парадигма - ООП. Вы не в силах это изменить, но яростно гребёте против течения. Вместо того, чтобы выбрать подходящий ЯП себе по вкусу.

Чем же вы тогда лучше так ненавистных вам программистов на Java и C#? Да по сути ничем, вы так же как и они продолжаете пользоваться ОО-языками с элементами ФП.

полиморфизм в стиле ООП, который в TypeScript является основным вариантом полиморфизма, и довольствуетесь switch/case.

Ошибка, в статье привел полиморфизм аж трех видов, и в спорах с ООП-шниками привел пример вообще без switch / case, на обобщениях, когда пользователь библиотеки может спокойно добавлять новые инструменты, генерируя на лету.

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

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

Отлично, может когда нибудь на Elixir начну писать, но пока на нем нет возможности делать мобилки / веб приложения и многое другое.

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

 привел пример вообще без switch / case, на обобщениях

Не хочу вас расстраивать, но обобщенное программирование - это тоже отдельная парадигма :-)

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

Автор библиотеки должен предусмотреть, что надо использовать не библиотечную функцию, а вашу? Прям даже интересно, сможете ли вы найти хоть одну реальную либу с таким "расширяемым" switch / case.

Ну, т.е. сделать то в таком стиле можно, но там switch / case вообще не нужен. Просто сигнатуру лямбды фиксируешь и даёшь возможность её в настройках либы указать. Но это несколько другая история.

Отлично, может когда нибудь на Elixir начну писать, но пока на нем нет возможности делать мобилки / веб приложения и многое другое.

Если вас исключительно фронт и мобилки интересует, то ваш выбор: ReScript, есть и под React, и под React Native, и под много что ещё биндинги. Почитайте сравнение с TypeScript в идеалогическом плане.

А бек, IoT, ML и интерактивчики - лучше на Elixir

В общем, слезайте уже с объектно-ориентированных Go и TypeScript. А то весь ваш посыл выглядит неконгруентно, как "пчёлы против мёда".

пока на нем нет возможности делать мобилки / веб приложения и многое другое

Как вам там в 2013 году живется? Нормально все?

"Заставь дурака ООП следовать, он FizzBuzzEnterprise напишет."

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

Есть такая фраза: "use the right tool for the right job". Если использовать инструмент неправильно или в не подходящих условиях, то кто сам себе злобный Буратино?

Существует, и я объяснил аргументами и кодом почему. Где ваш код?

Ну я в Go активно применяю ООП паттерны и отлично живу.

Утиная типизация вообще маст хэв.

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

Какой-то треш... Автор не понимает ни концепции ООП, ни ФП, зато понимает TypeScript и ловко владеет всеми возможными способами прострелить себе колени

Что конкретно автор не понимает и в чем ошибка в статье конечно же опустим, ведь сами не знаем.

согласен с автором ООП - это полное говно!

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

Похоже, ТС виртуалов плодить начал 😄

Похоже сарказм никто не распознал

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

Закон По: «Невозможно создать пародию на экстремизм или фундаментализм без явного указания, что это пародия, чтобы не нашёлся человек, который принял бы всё за правду».

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

Проблема банана и макаки, мне кажется, может решаться архитектурно, то есть, даже на примере того же asp net core ты не получишь вместе с макакой "все остальное" если используешь грамотно 4 принцип солид. Грамотно оперируя абстракциями таких проблем не возникает, то же и касается внедрения зависимостей, контейнеров и создания инстансов, проблема, которую решает 5 принцип солид. Статья объемная, интересная для размышления, но как и полодено в таких докладах, не совсем могу согласиться

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

Чтобы лучше понять разницу между функциями и процедурами, можно рассмотреть понятия переменных (variables) и "присваеваемых" (assignables). Не знаю, как лучше перевести, можно думать о них, как о ячейках памяти. Часто в литературе их также называют переменными, что скрывает различие между variable и assignable. Переменные вводятся с помощью функциональной абстракции, а смысл им придается с помощью подстановок. Например, 1-арная лямбда-функция связывает переменную-аргумент с выражением: lambda(x){x+3}(2); -> 5 "Присваевыемые" вводятся с помощью объявления, а смысл им придается с помощью присваивания и возвращения содержимого. Например, 0-арная лямбда-функция может использовать значение ранее объявленной "присваеваемой": declare x=2; lambda(){x+3}(); -> 5

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

Процедура — это функция, которая возвращает невыполненную команду в качестве значения. Вызов процедуры — это композиция применения функции (возвращающей команду) и вызова этой команды.

Подробнее об этом, например, в Harper: Practical Foundations for Programming Languages

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

const getDisplayName = (user: {firstName: string, lastName?: string, middleName?: string} | null | undefined)

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

Ну и классическая фронтэндовская история, когда бардак в кодовой базе приводит к тому, что никто вообще не понимает, когда откуда-то прилетает null, а когда undefined, прям вишенка на торте.

Предлагаю усилить позицию:

Не только ООП говно, но и все языки со строгой типизацией! Ибо нехуй!))

Мира всем

При чем тут вообще строгая типизация?

О, да, бро, прости, ты конечно прав!

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

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

Наверное ядро Линукс на сях написанное

Не-а: https://lwn.net/Articles/444910/

GCC тоже почему-то на С++ перевели уже давно. Правда, я не знаю, применяется ли там ООП, не смотрел в код.

Можно привести в пример glibc, он до сих пор на чистой сишке, но я опять же не в курсе, применяется ли там ООП.

GTK тоже на сишке, но там используется ООП (GObject).

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

Я пошутил, думаю есть достаточно крупные проекты, где по техническим причинам не применяется ООП. Хотя я смотрю, что даже а ардуинкаких какое-никакое ООП используется. Помню когда заинтересовался программированием на ассемблере, учился по книжке Питера Нортона, в которой он писал графический дисковый редактор на ассемблере, а в предисловии было написано, что на ассемблере написано Lotus 1-2-3

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

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

Могу ошибаться, но Team Fortress 2 вроде был написан без ООП из-за чего там вермишель из кода которая вызывает баги при обновлениях до сих пор и есть части кода любые изменения в которых вызывает неработоспособность всего кода игры . Да и в целом многие игры до 00-10 годов писались без ООП, что вызывало отсутствие патчей первого дня как сейчас, патчи выходили спустя месяц-два, как из-за отсутствия ООП, так и из-за не распространенности интернета

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

Коротко тут никому не надо. Заметил ошибку - указал, считаешь что ООП лучше - привел код в каких случаях.

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

Статья без критического мышления: ФП круто, ООП не круто. Здорово быть фронтендером где как раз сейчас мода на функционально-реактивное программирование. Там это классно ложится. В то же время созданный вами образ "ООП для дураков 99% населения" ИМХО как-никак стандартизирует код и заход в кодовую базу ощущается легче. Я молчу вообще про чистое ФП, где вместо решения бизнес задач люди будут спорить о монадах и чистоте функции.
Здорово было бы посмотреть критически на свои тезизы, у всего есть недостатки и слабые места.
Лично мне понравился пример JS/TS - но это отнюдь не функциональный язык, это мультипарадигменный язык. Там есть и крутые вещи из ФП - каррирование, лямбы итд, так и из ООП в виде прототипов/классового сахара.

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

Автору нужно миску риса выдать за такой веселый накид на вентилятор. С огромной радостью бы посмотрел как автор применяет функциональный стиль при работе со строками или массивами. Или это другое?

Ну массив и строка (которая почти везде массив) - легко обрабатываются в ФП. Более того, современные тренды тянут ФП на работу с массивами - всякие map, reduce и т.д., принимающие функцию обработчик - вполне функционально, в JS массивы с их методами - буквально монада

Думаю, имеется в виду, что во многих ФП языках нет такого понятия как массив, есть только связный список. Да и в целом все данные неизменяемые, поэтому легко придумать синтетический тест, который будет постоянно что-то менять in-place в массивах, классическая Game of Life например, и ФП-реализация очевидно проиграет по производительности.

Линейные типы завезли уже очень давно.

Вы бы хоть уточняли, куда завезли и как там у них с мутабельностью?

Вы бы хоть уточняли, куда завезли

Математическая теория — лет 40+. Clean — изначально там были, Idris — с ЕМНИП 0.9.15 (лет 8 уже), GHC — 9.0.1 (года четыре как).

и как там у них с мутабельностью?

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

С теорией то всё понятно. В теории код не надо на реальном железе выполнять.

старым именем переменной после мутации пользоваться нельзя

т.е. всё-таки мы получаем ссылку на другую область памяти, а не изменяем данные in-place?

Нет. Вот у вас есть код на Клине, изменяющий массив:

quicksort arr lo hi
            | lo >= hi  = arr
            | otherwise = quicksort arr`` p hi
                          where arr``     = quicksort arr` lo (p - 1)
                                (p, arr`) = partition arr  lo hi

В нём arr``, arr` — это тот же самый массив, что и arr, физически. Но просто получивший другое имя. И работа вся идёт без копирования.

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

Вот у вас есть код на Клине

Вы бы хоть не поленились ссылку на сайт этого ЯП дать или нормально его название по-английски написать. Первый раз про такой язык слышу, как и гугл xD

И работа вся идёт без копирования.

Из приведённого примера это вовсе неочевидно. Более того, его можно 1-в-1 переписать на любом языке с иммутабельными структурами данных. Т.е. он вообще наличие мутабельности никоим образом не доказывает.

Можно что-то более наглядное? Например:

arr = [1, 2, 3, 4, 5]
arr` = change_nth_value(arr, 0, 7)
print(arr[0] == 7)

https://clean-lang.org/

Ну вы примерно и написали, с точностью до синтаксиса, что характерно. Только в последней строке используется arr`, а не arr. Массив arr "съеден" изменяющей его функцией change_nth_value.

Подозрительно это всё. Получается, на уровне кода не показать in-place мутабельность. А есть какие-нибудь бенчмарки сравнения с какой-нибудь Java? Обычно, в бенчмарках сплошь и рядом алгоритмы, опирающиеся на работу с массивами.

Вы просто не понимаете язык. Если бы вы не знали, что означает Сшный знак =, вы бы тоже говорили, что «на уровне кода не показать in-place мутабельность».

По спецификации языка функция update изменяет ячейку физического массива, убивая привязку старого имени и создавая новое имя

https://cloogle.org/src/#base-stdenv/_SystemArray;icl;line=37

Если хотите проверить гарантии mutable update — ну флаг вам в руки.

т.е. всё-таки мы получаем ссылку на другую область памяти, а не изменяем данные in-place?

Нет, почему? Область памяти та же, просто имя другое.

Условно, в хаскеле с линейными типами операция записи в массив выглядит как

write :: Int → a → Array a %1→ Array a

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

По аналогичным причинам чтение выглядит как, несколько упрощая,

read :: Int → Array a %1→ (a, Array a)

и старым именем пользоваться нельзя, хотя чтение-то уж точно ничего не копирует.

Можно небольшой пример? Создаём массив со значениями от 1 до 5, меняем его первый и последний элемент местами, выводим весь массив на экран.

Да. конечно. Вот хаскель:

import Data.Unrestricted.Linear qualified as L
import Data.Vector.Mutable.Linear qualified as VL
import Prelude.Linear qualified as L

fillVec :: Int → VL.Vector Int %1→ VL.Vector Int
fillVec n = go 0
  where
  go :: Int → VL.Vector Int %1→ VL.Vector Int
  go v vec
    | v == n = vec
    | otherwise = go (v + 1) L.$ VL.push (v + 1) vec

swap :: Int → Int → VL.Vector a %1→ VL.Vector a
swap p1 p2 vec =
  let %1 (L.Ur v1, vec¹) = VL.get p1 vec
      %1 (L.Ur v2, vec²) = VL.get p2 vec¹
   in VL.set p1 v2 L.$ VL.set p2 v1 vec² 

main :: IO ()
main = do
  let L.Ur vec = VL.empty (VL.freeze L.. swap 0 (n - 1) L.. fillVec n)
  print vec
  where n = 5
> :main
[5,2,3,4,1]

Или идрис:

import Data.Linear
import Data.Linear.Array

run : Int -> IO ()
run size = newArray size (\1 arr : _ => toIArray (swap 0 (size - 1) $ fill 0 arr) printArr)
  where
  fill : Int -> LinArray Int -@ LinArray Int
  fill n arr = if n == size
                  then arr
                  else let _ # arr = write arr n (n + 1) in fill (n + 1) arr

  swap : Int -> Int -> LinArray Int -@ LinArray Int
  swap p1 p2 arr =
    let mv1 # arr = mread arr p1
        mv2 # arr = mread arr p2
     in case (mv1, mv2) of
             (Just v1, Just v2) => let _ # arr = write arr p1 v2
                                       _ # arr = write arr p2 v1
                                    in arr
             _ => arr

  printArr : IArray Int -> IO ()
  printArr arr = for_ [0..size - 1] $ \i => case read arr i of
                                                 Just v => printLn v
                                                 Nothing => pure ()
Main> :exec (run 5)
5
2
3
4
1

Спасибо, интересно. По многословности кода понятно, что всё-таки работа с массивами - это edge-кейс для этих языков.

В каком-нибудь Ruby это делается сильно проще:

arr = [1, 2, 3, 4, 5] # либо (1..5).to_a
arr[0], arr[-1] = arr[-1], arr[0] unless arr.empty?
puts arr.inspect # => [5, 2, 3, 4, 1]

В неискусственных задачах лучше выходит. Вот, например, кусок NFA-интерпретатора с иммутабельными векторами:

match :: StateId q => NFA q -> BS.ByteString -> MatchResult Int
match NFA{..} bs = go initState 0 mempty
  where
  go q i stack
    | q == finState = SuccessAt i
    | otherwise = case q `getTrans` transitions of
             TEps q' -> go q' i stack
             TBranch q1 q2 -> go q1 i (stack `V.snoc` (q2, i))
             TCh ch q'
               | bs `BS.indexMaybe` i == Just ch -> go q' (i + 1) stack
               | Just (stack'', (q'', i'')) <- V.unsnoc stack -> go q'' i'' stack''
               | otherwise -> Failure

Вот он же с мутабельными линейными:

match :: forall q. StateId q => NFA q -> BS.ByteString -> MatchResult Int
match NFA{..} bs = L.unur L.$ VL.empty L.$ go initState 0
  where
  go :: q -> Int -> VL.Vector (q, Int) %1-> L.Ur (MatchResult Int)
  go q i stack
    | q == finState = stack `L.lseq` L.Ur (SuccessAt i)
    | otherwise = case q `getTrans` transitions of
                    TEps q' -> go q' i stack
                    TBranch q1 q2 -> go q1 i L.$ (q2, i) `VL.push` stack
                    TCh ch q'
                      | bs `BS.indexMaybe` i == Just ch -> go q' (i + 1) stack
                      | otherwise -> case VL.pop stack of
                                      (L.Ur top, stack'')
                                        | (Just (q'', i'')) <- top -> go q'' i'' stack''
                                        | otherwise -> stack'' `L.lseq` L.Ur Failure

Автор топит не за функциональный (= математический стиль), а за ФП. А в нем можно работать как в мутабельном, так и в иммутабельном стиле.

А в чём вы видите проблема с массивами и строками?

Язык R, занимающий уважаемое положение в матричных вычислениях – вполне функциональный. Хотя лично я для таких действий предпочту Фортран.

Вы вызываете стороннюю функцию чтобы добавить элемент в массив вроде addElementToArray(array, element) или все таки используете богомерзкий array.add(element) которому (о боже!) "массив передается неявно первым аргументом" и "его нельзя переиспользовать чтобы добавлять подливы в свой гуляш".

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

Не очень понял ваш аргумент, потому что в функциональных языках, конечно, используется первый вариант. В частности, в R это c(v, e).

Другое дело, что это противоречит иммутабельности.

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

// Функция.
const getDisplayName = (user: {firstName: string, lastName?: string, middleName?: string} | null | undefined) => {
  if (!user) return undefined

  return [user.firstName, user.middleName, user.lastName]
    .filter(Boolean) // ой, а что это у нас такое?
    .join(" ") // и здесь! безобразные методы!!!!!!
}

Автор, конечно, не без греха, но я не согласен, что ваш пример с массивом добавляет какую-то органичность. Напротив, он достаточно искусственен при дальнейшем применении, так как вам в нотации с методами неизбежно придётся писать что-нибудь вроде a.add(b), что противоречит представлению о сложении, как о симметричной (коммутативной) операции. Или даже совершенно противоестественное " ".join(v), как в Питоне.

Что же касается нижнего примера, то автор здесь является заложником синтаксиса используемого им языка. Семантически, написана-то просто суперпозиция функций.

Сам по себе тот факт, что вы синтаксически запишете первый аргумент функции через точку, не сделает вашу программу объектно-ориентированной.

код недостаточно ООП-шный — там нет ни одной фабрики и ни одного контейнера зависимостей.

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

Какая-то анти-статья, простите. Хорошее название и преамбула правильная, но аргументы с примерами кода подобраны букально по анти-паттрнам ФП и вообще программирования, как будто специально для того чтобы тебя разнесли оппоненты

Жду ваши крутые примеры. Вряд ли дождусь конечно.

С примерами кода обязательно.

тебя просили примеры кода, а их нет

Конструктивно было бы привести пример с кодом

Может скинете небольшой кусочек

Talk is cheap, show me the code (c).

Ну так перепишите и убедите, я свой код скинул. А без кода это демагогия.

Пока что даже одного "правильного" куска кода не набралось. Ждем, не дождемся

тут целая статья так то, с примерами кода.

Так приведите примеры кода, где якобы правильно все сделано. Я свой код привел. Без кода общение не конструктивно.

Talk is cheap, show me the code (c).

Talk is cheap, show me the code (c).

Talk is cheap, show me the code (c).

и я объяснил аргументами и кодом почему. Где ваш код?

Жду ваши крутые примеры. Вряд ли дождусь конечно.

Поздравим автора с не-использованием слова "код" в комментарии.

Сдается мне, тут просто слишком широко трактуется термин "данные". Вот есть например окно. У него есть куча т.н. "данных": координаты, размер, цвет, прозрачность, маржины там всякие, оффсеты.... И вот у меня есть объект Window, который все нужное хранит у себя внутрях в удобном себе виде и избавляет меня от необходимости таскать все это отдельно - так это же прекрасно.

И есть бизнес-данные, с которыми мы работаем, которые мы получаем с веба, пишем/читаем в базу, как-то обрабатываем, сериализуем/десериализуем, показываем пользователю. Тот же User из примеров. И тут уже функциональный подход смотрится интереснее: отдельно есть структуры, в идеале иммутабельные, содержащие только данные, и отдельно методы их обработки. Всякие там фильтры, мапы, трансформы, аггрегирующие функции, да с возможностью комбинации, да еще с concurrency - разве это не прекрасно. Реактивщина к тому же легко подвязывается, тестировать именно бизнес-логику удобно.

Тот же getDisplayName() я бы вообще не стал пихать в класс/структуру User, ибо это не его дело знать, как там выводить надо. Может, в одном месте надо выводить "John Smith", в другом "J. Smith", в третьем "Smith, J.", а четвертом вообще "Mr. Smith" или какой-нибудь "Dr. Smith". Это надо делать на уровне представления в какой-нибудь вьюМодели, ну или сделать отдельный форматтер, если надо реюзать один формат в куче мест.

после этого примера перестал читать, сразу видно "фпшника"

Да там перл на перле. Предложить switch вместо полиморфизма это сильно - интересно будет посмотреть на случай, когда нужно будет переопределить часть поведения из библиотеки. Видимо fork->клац-цлац->готово

switch вместо полиморфизма

Узнай что такое полиморфизм сначала.

Вы всё с технической точки зрения смотрите. Но по факту ООП важен просто как способ описать интерфейс зон ответственности между разработчиками в команде. Ты пилишь класс, отдаёшь API и контракт другому, и он не напрягает мозг что там внутри.

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

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

Как будто что-то плохое.

Так зачем вам ооп тогда вообще, тс, Java итд? Хаскелл и вперёд в коммерцию. Через год вас оттуда тапком выгонят.

Надеюсь ваши продукты лучше, чем эта статья.

Практически вся коммерция на ФП сейчас. Клиентские (react, vue, redux, zustand и тп), серверные (go, nodejs express и тп). А это больше половины всех создаваемых программ.

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

Если ФП и ПП это одно и то же (что довольно спорно), то получается ФП появилось раньше ООП. И раз ООП всё-таки придумали - значит, были в ФП какие-то проблемы, которые требовали новых подходов. То, что вы этих проблем не видите - ну может быть это в вас дело, а не в отсутствии проблем

ФП в любом случае появилось раньше ООП.

значит, были в ФП какие-то проблемы, которые требовали новых подходов

Overqualification. Трудно, дорого и качественно, а рынку нужно по рабоче-крестьянски, дёшево и тяп-ляп.

Фп труднее ооп? Но ведь статья как раз про трудности с ооп в том числе. В чем сложность с фп? Сущностей меньше

Кстати в реакте ушли от классов потому что классы сложнее для джунов (лол), это в офф документации написано а не по иным причинам

Вообще зачем выбирать что то одно, я использую и фп и ооп одновременно где что лучше подходит а не нятягиваю сову на глобус

Я без классов не могу вообще. Поэтому либо PHP, либо Vuejs Options API

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

следующая тема метка, пометить тоесть засечка/разметка

ФП обычно идёт неразрывно с неизменяемыми структурами данных. Это уменьшало производительность программ, актуальных в 80-90 годах. Сейчас уже разница в производительности не особо велика в подавляющем большинстве случаев, т.к. компиляторы уже научились многое оптимизировать. Зато ФП сильно повышает надёжность программ.

Почитайте секцию определений - ФП у меня это не математический стиль ФП.

Вы лучше сами почитайте определение ФП: https://en.wikipedia.org/wiki/Functional_programming

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

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

Вы просто взяли определение процедурного программирования xD

А функциональное обозвали математическим стилем. Это тупая подмена понятий.

  1. Я добавил определение процедурного стиля, и оно совсем отличается от моего Фп. Что в нем не так?

  2. В примерах я возвращаю из функций новые функции с замыканием - это ПП?

Я добавил определение процедурного стиля, и оно совсем отличается от моего Фп. Что в нем не так?

ПП - это не стиль ФП, это совсем разные парадигмы. Так что в ваших определениях всё не так. Почитайте хотя бы Википедию, если книги по Computer Science осилить не можете. И не выдумывайте собственных определений. Берите готовые и давайте ссылку на источник определения.

В примерах я возвращаю из функций новые функции с замыканием - это ПП?

Это элементы ФП, но отдельных элементов ФП недостаточно, чтобы назвать язык функциональным. Должен присутствовать полный комплект.

Это элементы ФП, но отдельных элементов ФП недостаточно, чтобы назвать язык функциональным

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

Я решил назвать это функциональным программированием. Не согласны - ок. Только предложите свое логичное название или не спорьте тогда.

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

Языки, в которых есть элементы разных парадигм, называются мультипарадигмальными. Зайдите на ту же вики и почитайте элементы каких парадигм есть в TypeScript. Там их штук 7 где-то, а вы пытаетесь всё упростить, где не надо.

Если вы будете писать для каждой задачи в разных парадигмах, у вас получится каша, которую потом будет невозможно потом поддерживать. Да и написано будет плохо, потому что если вы постоянно в разных парадигмах будете писать, вы или гений программирования, или вы ни одну как следует чувствовать не будете, чтобы на уровне автоматизма хорошее решение выбирать. Если конечно "задача" - это не система уровня энтерпрайз на человеко-десятилетия работы. Но зачем вам тогда самому писать на чём попало, у вас будет команда со специализацией, кто-то будет писать с классами, а кто-то без :)

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

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

Вы правы, ПП и ФП- это абсолютно разные подходы (императивный и декларативный), а не просто разные парадигмы.

И еще ФП действительно появились раньше чем ООП. Lisp появился в 1958, а первая версия первого ООП языка Simula появилась в 1964 (более популярный Smalltalk только в первой половине 80х)

Ну где вы нашли декларативность в ФП? Там же императивы кругом: возьми эту функцию, вызови её с теми аргументами, результат засунь в ту функцию. А мне нужен невидимый розовый единорог, а не функции ваши.

Я нигде ничего не ищу. Это положения науки, которую преподают в ВУЗах - "Computer science" (Информатика):

Common declarative languages include those of database query languages (e.g., SQL, XQuery), regular expressions, logic programming (e.g. Prolog, Datalog, answer set programming), functional programming, configuration management, and algebraic modeling systems.

Ох уж эти "положения науки" из википедии. В нашей вот учат совсем другому:

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

Подробнее я разбирал этот вопрос тут.

Тут даже из формулировки следует, что делают это не все и не всегда. Что логично, ведь притянуто такое отношение откровенно за уши:

Несмотря на то, что программы на таких языках нередко содержат алгоритмические составляющие, архитектура в императивном понимании (как нечто отдельное от кодирования) в них также отсутствует: схема программы является непосредственно частью исполняемого кода.

Какой-то бессмысленный набор слов от неизвестного автора википедии с отсылкой на нерелевантное интервью какого-то грантоеда.

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

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

Может, чего надумает и более серьёзное сравнение сделает, а не на уровне "да в ФП есть полиморфизм, смотрите как красиво и понятно" и дальше отвратительная борода, проблемы которой прекрасно известны уже лет 50 как минимум

но это ничего, дальше будет лучше!

смотрите пример,

https://github.com/kanaka/mal/blob/master/process/stepA_mal.png

значит рантайм это цикл всё остальное структурки и списки

буквально на 1 односвязном списке и хэш-мапе можно сделать поидее с разными структурками и 1 реплом

и тоже самое можно сделать на ООП наверно

Нет ну точно требуется тег наброс на вентилятор😂😅

Я бы сказал - отдельный хаб)

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

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

Ожидал увидеть статью про ФП, но увидел в основном процедурное. разочаровался :(

Ничего себе хабросуицид!

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

То что в посте называется ФП на самом деле не особо функциональное программирование. Это скорее комбинация процедурного с использованием ФП. Популярно в go, в целом хорогий подход.

Код автора приведен по ссылке. Другим словом его назвать язык не повернется.

Во-первых, автор сам пробовал писать бизнесовый софт на чистом С? Не мелочевку для контроллера или что-то системное, а, предположим, гуй с 20-40 разными формами, списками, картинками, скинами, желательно еще с анимацией... Я попробовал - проклял всё, и перевел всё в плюсы. На пятой форме на С в коде получается лапша типа доширак, фактически, оно write-only, разобрать это даже тому, кто писал, через полгода невозможно. Именно потому в тех же линуксах во многом софте такие кондовые гуи, как будто их писали в 1991 году под пентиум первый.

Во-вторых, бизнесу не надо технически красиво. Бизнесу надо закрывать текущие потребности быстро и дешево. Еще ему надо, чтобы поделка была внешне красивой для конечного пользователя. Слова "качество" тут нигде нет, хотя все и говорят с напыщеным видом про него... ООП идеален для этого класса задач. А кто хочет перфекционизма - так идите в секретные госы ракеты делать, или в b2b-медтехнику, авиастрой, или еще куда в ответственные сферы - там будете килобайт ассемблерного кода месяц полировать до идеала.

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

месяц

Несколько лет, вы хотели сказать.

имя папки название абстракции внутри abstraction

#include <../b>
struct a
{
  char t;
};
//методы по принципу (a,и тут что нужно для абстракции)
//если абстракция пользуется другой вашей абстракцией то ее просто подключаем и она вторая по важности за хендлером в функции
//(a,b,и тут что нужно)
// у анимации много абстракций это будет блок анимации - которой будет заниматься грубо говоря не 1 абстракция (тоесть анимация будет в 1 папке но состоять из абстракций, а если навалить всё валом в тот же мейн то да будет перебор по пересечению абстракций) //но я не про бизнес просто знаю что так

на С++ будет быстрее, с абстракциями будет таже история - абстракций будет много(папок и там и там будет много, в С++ чуть удобнее будет)

Есть Rust, который как раз из разряда "быстро и дешево", и никаких классов нет.
Причем, "быстро" во всех смыслах :)

А ещё есть важная вещь которую кажется никто не упомянул об ООП. О том что автор этого самого ООП - Алан Кей предполагал совершенно другое. А то что сделал с++ это максимальное извращение и изнасилование первоначальных принципов ООП. Вот то что сейчас называют акторной моделью ближе всего к оригинальному ООП.

Тотже эрланг где нет ни объектов ни классов больше ООП(в понимании Алана Кея) чем плюсы или джава.

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

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

А ещё ведь из подобного массового неверного понимания есть REST API. С этим термином поступили ещё жёстче даже чем с ООП.

Да, встречал такое, что любые вызовы по HTTP называли REST API.

Самое смешное, что я встречал, когда говорили "у нас REST level 1", ссылаясь на https://martinfowler.com/articles/richardsonMaturityModel.html

При том, что в этом же документе прямым текстом написано:

I should stress that the RMM, while a good way to think about what the elements of REST, is not a definition of levels of REST itself. Roy Fielding has made it clear that level 3 RMM is a pre-condition of REST

Objects are merely a poor man's closures. Closures are a poor man's object.

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

Когда увидел название статьи и понял, что в комментариях творится сущий ад)
Очень хороший ответ дал выше @markelov69, но у меня есть желание дополнить.

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

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

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

Да вы что) Видно, что автор даже не понимает, про что пишет. Никто конструктор антипаттерном не считает. Да, писать в нем логику плохо, но это вообще не то, про что вы пишете дальше.

Более того, а должен ли класс вообще знать что он — синглтон?

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

Для идеальной гибкости конечно же нет. Вдруг кому то когда то понадобиться сделать синглтон не синглтоном? Такого еще в истории не было ни разу, но почему бы не написать еще больше кода, еще сильнее усложнив его.

Берешь и создаешь класс напрямую или через фарбрику. А про усложнение вообще бред: контейнер идет из коробки любого фреймворка, autowire сам подгружает нужные зависимости (Кроме случаев, когда у нас несколько реализаций интерфейса, например, тут будь добр выбирай), тебе даже делать особо ничего не надо

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

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

class User {
  update() {
    service.updateUser(this.id, …)
  }
}

// Правильно ли обновлять массив user таким образом?
// Что тут должен придумать неокрепший ум начинающего ООП-шника?
for (let user of users) {
  user.update()
}

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

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

Вы тут сами описали, в чем проблема функционального программирования. Глядя на того самого User, мы не можем понять, что он может: какие у него бизнес-правила, модель тупо плоская. Все размазано по всему проекту, нет четкого отделения предметной области, такое очень тяжко поддерживать.

Я ни в коем случае не считаю фп плохим. Я сам очень много писал на реакте и рад, что застал эру именно функциональных компонентов.

Оскорбления - отдельная тема, статью будто Стас ай как просто писал, уровень дискуссии в Восточной Европе как всегда высок.

Рефлексия может и костыль, но она довольно неплохо закрывает такие задачи.

Зато достать значение из JS-замыкания в любимом языке автора — вообще не получится

тогда надо создать структуру или функцию, которая обновляет юзеров

вот если паттерн там нужен пока не думал, типо оповещения о состоянии

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

Мне кажется, за последние 50 лет термины уже должны были устояться ;) Я бы предложил полистать книжку: Гради Буч, Объектно-ориентированный анализ и проектирование с примерами приложений.

Структура [данных] — чистые данные, не содержащие логики обработки (функций).

Эмммм. Структура - это обычно логически связанные данные. Я не знаю, что такое чистые данные, но думаю что что-то вроде int или float.

Функциональное программирование (ФП) — это программирование используя структуры и функции. Не путать с функциональным стилем.

Не увидел у вас в тексте определения термина Функциональный стиль. Хотя ниже есть математический.

По поводу данной особенности даже среди ООП-шников давно выработалось правило — наследование это антипаттерн, и нужно предпочитать делегирование.

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

Полиморфизм — способность функции обрабатывать данные разных типов.

Судя по определению - это перегрузка.
Полиморфизм же - это когда имя (метода) одно, а операции за ним скрываются разные.

Многие мастодонты программирования, такие как Линус Торвальдс, сразу же пришли к похожему выводу, и последний запретил использование языка C++ в ядре Linux.

Если верить гуглу: "В первую очередь, Линус говорит, что использование C++ неизменно приводит программиста к плохим решениям вроде использования библиотек STL или Boost, которые считаются прекрасными. Однако на деле, по мнению Линуса, они постоянно ломаются, отладка кода становится испытанием, а сам код нестабильный и не портируемый."

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

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

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

Я немного погуглил... Вот интервью с Джеймсом Гослингом, от 21 года, в котором сказано буквально следующее: "Джеймс: Я не думаю, что стоит отказываться от классов. Я обнаружил, что классы довольно хорошо справляются с композицией."
(с) https://evrone.ru/blog/interviews/james-gosling-interview

Можете привести источник, где Гослинг бы говорил, что надо отказаться от классов/наследования?

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

Мне кажется, за последние 50 лет термины уже должны были устояться ;) Я бы предложил полистать книжку: Гради Буч, Объектно-ориентированный анализ и проектирование с примерами приложений.

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

Знаете игру "Испорченный телефон", где люди передают друг другу информацию по цепочке? Вот Гради Буч в этой игре - тот самый человек, который и сам ничего не понял, и после него вся остальная цепочка идёт по п*зде.

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

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

А можно, пожалуйста, примеры статей, в которых есть наследование и т.д. и без привязки к ООП? Просто кроме Буча есть ещё Страуструп, Мейерс, Фаулер... И вроде бы все они привызывают ООП к наследованию и остальным двум китам.

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

Ну, вот смотрите, возьмём чисто функциональный Elixir и посмотрим, как там ключевой компонент стандартной библиотеки (GenServer) реализован:

Вот код базового модуля: https://github.com/elixir-lang/elixir/blob/v1.18.2/lib/elixir/lib/gen_server.ex#L844

А потом он просто подключается через use GenServer и любая функция, которая объявлена через defoverridable может быть переопределена в конечном модуле, а если не переопределишь, то будет взята реализация из базового модуля, см. пример тут: https://hexdocs.pm/elixir/GenServer.html

Что это, если не наследование реализации? Даже статьи искать не надо, из коробки уже есть.

P.S. По поводу детища Страуструпа есть прям цитата от автора ООП: "I made up the term object-oriented, and I can tell you I did not have C++ in mind."

Ну, вот смотрите, возьмём чисто функциональный Elixir и посмотрим, как там ключевой компонент стандартной библиотеки (GenServer) реализован:

Вот код базового модуля: https://github.com/elixir-lang/elixir/blob/v1.18.2/lib/elixir/lib/gen_server.ex#L844

А потом он просто подключается через use GenServer и любая функция, которая объявлена через defoverridable может быть переопределена в конечном модуле, а если не переопределишь, то будет взята реализация из базового модуля, см. пример тут: https://hexdocs.pm/elixir/GenServer.html

Что это, если не наследование реализации? Даже статьи искать не надо, из коробки уже есть.

Elixir - функциональный язык. defoverridable говорит о том, что можно переопределить функцию в конечном модуле. Это как если бы мы могли в С++ заменить функцию printf. Никакого ООП здесь нет. Плюс, здесь есть четкое разделение: либо старая функция, либо новая.

Наследование же предполагает, что мы берём существующий класс (не функцию), и заменяем в нём отдельные элементы, например виртуальные методы. Причём использовать мы можем как новый метод, так и старый. Да, некая схожесть имеет место быть, но в С++ мы можем пройти по массиву указателей на классы и повызывать один и тот же метод, но работать он будет по разному (полиморфизм... где-то метод заменили, где-то нет). Например, в графической системе можно хранить указатели на классы, под которыми будут скрываться треугольник, круг, квадрат, и один и тот же метод Paint будет рисовать каждую конкретную фигуру по разному (при этом каждая фигура унаследует метод Paint от базового класса, его координаты...). Можно такое сделать с помощью defoverridable?

P.S. По поводу детища Страуструпа есть прям цитата от автора ООП: "I made up the term object-oriented, and I can tell you I did not have C++ in mind."

Хорошо. Допустим он действительно пишет, что ООП не подразумевает С++. Но ведь он не пишет, что ООП может существовать без инкапсуляции, полиморфизма, наследования... Поэтому повторю свой вопрос из предыдущего коммента: А можно, пожалуйста, примеры статей, в которых есть наследование и т.д. и без привязки к ООП? Вы же сами сказали, что:

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

Никакого ООП здесь нет. 

Вот вы чем читаете? Я же пишу, что наследование, инкапсуляция и полиморфизм не имеют отношения к ООП. В этом и есть суть примера, что в нём нет ООП, а наследование реализации - есть.

defoverridable говорит о том, что можно переопределить функцию в конечном модуле

И что? Точно так же модификаторы public/protected говорят, что можно переопределить метод в какой-нибудь Java или C#

Плюс, здесь есть четкое разделение: либо старая функция, либо новая.

Не угадали. Можно из новой вызвать старую: https://onecompiler.com/elixir/43aen3cqd

Наследование же предполагает, что мы берём существующий класс 

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

Но ведь он не пишет, что ООП может существовать без инкапсуляции, полиморфизма, наследования... 

Пишет. Вот ещё одна цитата от 2003 года (чтоб уж точно понимать, что Java и C# тоже по кривой дорожке пошли): "OOP to me means only messaging, local retention, and protection and hiding of state-process, and extreme late-binding of all things. It can be done in Smalltalk and in LISP. There are possibly other systems in which this is possible, but I'm not aware of them."

Чуть позже он ещё признал, что языки на базе BEAM тоже реализуют концепцию ООП за счёт наличия акторной модели.

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

Я ж вам пример наследования без ООП уже привёл. В разных функциональных языках это делается по разному. В Haskell через ADT, в CommonLisp через CLOS, которая все эти фишки на базе замыканий реализует. Я не вижу смысла закидывать вас десятком ссылок, поскольку это просто распылит обсуждение. А вы пока не признали, что наследование реализации возможно без ООП, хотя я вам доказательство привёл. Ведёте себя как зашоренная лошадь: "нет-нет, наследование должно быть только таким как я привык, где классы, без классов не бывает наследования", ну смешно, право слово.

Давайте тогда уж дадим определение: наследование реализации - это возможность создать один или несколько блоков кода (классов/модулей/пакетов/etc.) на базе другого (базового) без необходимости делегировать вызовы вручную (в противоположность композиции). Основная цель наследования - избежать дублирования кода реализации, т.е. функций/методов. Профит: общая точка для изменений, т.е. изменения в базовой реализации раскатываются на все дочерние. Согласны с этим?

Вот вы чем читаете? Я же пишу, ...

Хамить не обязательно.

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

А в чём тут хамство то?

А это называется газлайтинг ;)

И я вам откомментил тот кусок, который про хамство.

Это было искреннее удивление вашему умению так быстро переобуваться. Простите, не хотел вас этим оскорбить.

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

Из вас получится отличный тролль.

Извинится, и наехать снова.

Давайте я что ли вас же и процитирую:

Вы: Ну, а раз по существу ответить нечего,

плюс из предыдущего:

Я > А можно, пожалуйста, примеры статей,

Вы: Ну, вот смотрите, возьмём чисто функциональный Elixir

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

и примеров сотни статей я так и не дождусь

Я думал, что вы умеете гуглить по ключевым словам.

Вот вам про наследование реализации через тайп-классы, через макросы, через замыкания.

Определение ООП без наследования и полиморфизма

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

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

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

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

Что за бред и в целом Богомерзкая концепция?! Ну если к примеру человек пишет драйвера на чисто С, то ясен пень никакое ООП ему не нужно. Но ничего, если ч то-то более менее сложное, требующее Архитектуры, без ООП вообще невозможно?! Ну давайте теперь, я не знаю, SOLID выбросим за ненадобностью, начнем поля везде публичные ставить, а то и свойства с Геттером и Сеттером поголовно повсюду...

Даже в C можно писать в ООП-стиле:

typedef struct Animal {
    const char* name;
    void (*say)(struct Animal*);
} Animal;

void Animal_init(Animal* animal, const char* name) {
    animal->name = name;
}

typedef struct {
    Animal base;
} Cat;

void Cat_say(Animal* animal) {
    printf("%s says: Meow!\n", animal->name);
}

Cat* Cat_new(const char* name) {
    Cat* cat = (Cat*)malloc(sizeof(Cat));
    if (cat) {
        Animal_init((Animal*)cat, name);
        cat->base.say = Cat_say;
    }
    return cat;
}

void Cat_delete(Cat* cat) {
    free(cat);
}

typedef struct {
    Animal base;
} Dog;

void Dog_say(Animal* animal) {
    printf("%s says: Bark!\n", animal->name);
}

Dog* Dog_new(const char* name) {
    Dog* dog = (Dog*)malloc(sizeof(Dog));
    if (dog) {
        Animal_init((Animal*)dog, name);
        dog->base.say = Dog_say;
    }
    return dog;
}

void Dog_delete(Dog* dog) {
    free(dog);
}

int main() {
    Cat* cat = Cat_new("Felix");
    Dog* dog = Dog_new("Lassie");

    Animal* animal1 = (Animal*)cat;
    animal1->say(animal1);

    Animal* animal2 = (Animal*)dog;
    animal2->say(animal2);

    Cat_delete(cat);
    Dog_delete(dog);

    return 0;
}

Даже в C можно писать в ООП-стиле:

Можно. Спасибо за пример!

Но вообще не понятно, зачем так делать в 21 веке.

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

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

Представьте, пожалуйста, Message Broker (Kafka/RabbitMQ) — как набор классов, свойств и методов. 

Достаточно посмотреть как в большинстве ООП библиотек выглядит представление http запроса/ответа - например cookies. Простейшие вещи усложнены на порядки.

Примеры кода, которые так хочет автор.

class Dog extends Animal { firstName lastName }
Как переиспользовать метод getDisplayName из класса User?

Никак. Какой блин еще lastName у собаки?)

class User {
  firstName: string
  lastName: string
  
  getDisplayName() {
    return this.firstName + ' ' + this.lastName;
  }
}

class Dog extends Animal {
  moniker: string

  getDisplayName() {
    return this.moniker;
  }
}

Но вообще getDisplayName не должно быть в этих классах, как уже сказали в этих комментариях:
1, 2

({firstName: "Alexander"}).getDisplayName() // Ошибка: у object нет такого метода.

let user: User | null
user.getDisplayName() // Ошибка: null reference.

1. И не надо. Нафиг мне вызывать несуществующие методы у объектов? Методы для того и группируются в классы, чтобы не вызывать то, что не нужно.
2. Ну так и хорошо, что она там есть. Нафиг мне в каждой функции, куда передается структура, проверять на null вручную?

getDisplayName(undefined) // undefined

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

void / virtual / override / sealed
Аналог переопределения метода в ~ФП~ ПП:
if (...)

Ну то есть вы предлагаете все эти ключевые слова реализовывать вручную? Нафиг мне это надо.
Тогда и оператор if не используйте, пишите на ассемблере с "cmp" и "je/ja/jb".

Аналог переопределения метода в ООП:

class A {
  public doSomething() { ... }
}

class B extends A {
  public doSomething() { ... }
}

Все настолько просто, насколько возможно. Кода уж точно меньше, чем с вашим if.

super(name, surname, "", []) // Обязаны предоставить поля, которые нам не нужны.
изменить изначальный код и разбить на более мелкие классы.

1. Если не нужны, нафига вы так пишете?
2. Не надо ничего разбивать. Это разные независимые сущности.

class User {
  id: string
  name: string
  surname: string
  address: string
  friends: User[]

  constructor(name: string, surname: string, address: string, friends: User[]) { … }
  
  getDisplayName() { return this.name + ' ' + this.surname; }
}

class Npc {
  npcName: string
  
  constructor(npcName: string) { ... }
  
  getDisplayName() { return this.npcName; }
}

Итог: наследование добавляет множество проблем, но не решает ни одной.

Неправильный итог. Из одного или даже 10 примеров нельзя сделать вывод, что оно не решает ни одной проблемы.

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

Даа, а сделайте-ка теперь так в языке C? Может все-таки тут дело не в ООП и ПП, а в возможностях TypeScript?

Функция "публичная", так как экспортируется.
export const getDisplayName = () => …
Как видим, все сценарии довольно просто реализуются без дополнительных символов в виде модификаторов доступа.

Даа, а "export" это не дополнительные символы?

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

А при чем тут ООП, если это зависит от компилятора языка?
На PHP с ООП код выглядит примерно так же.

class User {
  public function __construct(
    public string $id,
    public string $firstName,
    public string $lastName,
    public array $friendIds,
  ) {}
}

// Компилятор укажет на проблемы, если типы предоставляемых полей не совпадают.
$user = new User(
  id: "1",
  firstName: "Вася",
  lastName: "Петров",
  friendIds: ["2"]
);

В ООП языках для этого часто приходится реализовывать функции сериализации и клонирования в каждом отдельном классе

Это зависит от компилятора и рантайма, а не от ООП.

class Product {
  public function __construct(public string $id, public string $name) {}
}

echo json_encode(new Product('1', 'Test product'));
// {"id":"1","name":"Test product"}

Дальше неохота разбирать, коммент и так уже большой.

Забавный малый. Го у него без ООП, хотя это прямо второй вопрос на собесе: как же реализовано ооп в го?

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

Тайпскрипт? Так он же тоже оопэшный... Понимаю, автор закинул то, что кое-как осилил, плюс С. И все на таких серьёзных щах)

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

И где наша новая икона раст? Не осилил, понимаю.

Идти по каждому аргументу не имеет смысла. Ребенок глупый настолько, что даже не понимает, о чем пишет. Ну вот, например, рассказывает о страшных конструкторах, которые обязательно надо бойлерплейтить. И толку объяснять этому ребёнку, что не обязательно?))

Прекрасная иллюстрация, как за 13 лет можно только штаны просидеть

писать на плюсах православно, с шаблонами и без классов

Тогда в большинстве случаев придётся использовать указатели. Но что, все же, большее из зол?

Как одно связано с другим?

Можно не использовать. Согласно учению адепта ФП главное - не использовать методы через точку. А с этим в современных плюсах вообще никаких проблем даже при использовании контейнеров )

В вокабуляре адептов ФП отсутствует слово «метод» ввиду нерелевантности понятия как такового.

https://rustc-dev-guide.rust-lang.org/building/how-to-build-and-run.html, как собрать кланг, llvm, gcc чтобы раст был текущим или просто скипаем эти нюансы и скачиваем собранный раст? там есть нюансы?

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

допустим вы захотите программу какую-то перенесенную собрать и в ней Раст, я как то в такой программе поставил галочку и световой день собирал эту программу, потомучто ему надо было собрать std gcc clang, а там помимо раста тянулся питон например

на помощ конечно приходит собранная программа например ffmpeg, но с ффмпег есть нюансы на сколько я знаю (на ФриБСД из портов собирал)

Го у него без ООП, хотя это прямо второй вопрос на собесе: как же реализовано ооп в го?

В го нет наследования, так что вопрос дискуссионный.

почему опытные программисты Java (C#, C++ и т.п.) в принципе не могут считаться крутыми инженерами, а код на Java - хорошим.

Дядя, а не охренел ли ты?

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

Хабр, я требую объяснений. 71 + 71 = 145, -71 + 71 = 20 ?

Не все голоса равны.

А в чем прикол написать статю на ломаном английском и потом «перевести» ее на русский и назвать это переводом?

используйте языки, где нет классов (Go)

Поэтому там используют костыль, имитирующий классы )

Я абсолютно уверен, что человек, пишущий коммерческий код на одном из чисто ООП-шных языков более 3-4 лет, и который не заметил множество его проблем и не начал думать о переходе или переходить на ФП — не может считаться крутым инженером

Такие высказывания, как я считаю, полная ерунда и признак джунов или начинающих мидлов, которые цепляются за конкретный язык или технологию (особенно когда только что прочитали умную книгу или статью), и пытаются убедить всех, что все остальное это плохо, а вот это вот просто грааль и нужно использовать везде. Как раз таки признак крутого инженера с богатым опытом в том, что он в первую очередь решает задачи, а во вторую разбирается с языком (выбирает наиболее подходящий под задачу и с учетом опыта и компетенций инженеров в компании), т.к. зачастую мы попадаем в проект где уже все используется до нас и нам нужно продолжить работу, и если там для меня новый язык, то я пойду разбираться с ним, а не скажу заказчику, что его выбор плохой и нужно все переписать на моем любимом языке, затратив много месяцев и денег. Так в продуктовой разработке не работает. Крутой спец будет разбираться с тем, что досталось и не искать 100 минусов в ООП.
Такие инженеры-фанаты яп опасны еще и тем, что могут появляться внутри компании в рамках одной команды специфичные языки/технологии, которые не используют другие команды и в будущем, когда проект перейдет другой команде, то начнутся большие проблемы. Очень показательны примеры поиска спецов на Scala в разных компаниях, на котором мало кто хочет писать и для рекрутеров это большая головная боль, зато себе в карме человек поставил галочку - протащил свой любимый яп.
Мораль моего комментария - такие статьи очень опасны (особенно на хабре), т.к. молодые джуны и мидлы после прочтения уверуют эту идею, и будут потом всем доказывать, что ООП это зло, а ФП это круто и удобно.

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

Лапша никак не зависит от парадигмы.

ok. hello world.

Такое ощущение что статью писал человек которому так и не удалось перешагнуть порог "hello world" и вместо того чтобы работать над собой, автор решил убедить, что все вокруг не правы, а он с его "видением мира" молодец, собственно количество минусов у статьи подтверждает мои слова.

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

ФП - это как правило меньшая гибкость, нарушение многих принципов SOLID, сложности в поддержке и масштабировании ПО. Со временем это превращается в лютое легаси (https://ru.wikipedia.org/wiki/Большой_комок_грязи ) которое в итоге переписывается на ООП решение со всеми его прелестями (инверсия зависимостей, принцип единой ответственности, наследование, гибкость в случае расширения, применение DDD, более высокая скорость разработки на длинной дистанции и тд)

В 90% задач перед разработчиком стоит классическая задача реализовать некое ПО которое будет заниматься операциями ввода / вывода, будет легко поддерживаемым и масштабируемым, но никак не требуется "выжать все" и достичь фантастической производительности, а это значит ФП точно не вариант.

Именно поэтому ФП имеет смысл лишь в ПО

  • где нужно достичь максимальной производительности любой ценой

  • слишком сырое решение, например наколеночный "Proof of concept"

нарушение многих принципов SOLID

Это уже является самостоятельным составом преступления для суда инквизиции?

Помидор — плохо, потому что в нем нет мяса.

Именно поэтому ФП имеет смысл лишь в ПО

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

Согласен с вами, слово ТОЛЬКО было излишним, поскольку каждому инструменту есть свое применение как и сказал в комментарии выше. Здравый смысл и контекст всегда в первую очередь. А фанатичное следование лишь одному стилю, инструменту ни к чему хорошему не приводит как правило

Слушайте, я вас понял.

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

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

Конечно, в язык с ПП надо добавить возможности, которые ООП дает, в частности, Spring - бины или ссылки на модули, полиморфизм модулей, DI. Таким образом, чтобы язык процедурного программирования в конечном итоге реализовывал принципы SOLID.

Гляньте лучше про SOLID тут. Он больше создаёт проблем, чем решает. В частности, навязчивое желание ТС раздербанить единый User на множество одноразовых ad-hoc интерфейсов - это следствие ISP.

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

Вы принципиально не видите разницу между принципом и паттерном или только в даном случае?

отказывайтесь от языков, где классы это основа (Java, C#, C++ и др.). Пишите качественный — максимально простой, функциональный код.

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

Я предположил, что поклонники хаскеля массово обратились в новую секту - AI, GPT и вот это все.

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

Вас, как и многих других комментаторов, автор ввёл в заблуждение (не знаю, намеренно или по собственной искренней неадекватности) - он функциональным программированием называет процедурное, потому что там функции 😂

Называть функциональным программированием программирование функциями это очень смешно.

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

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

Нафига они мне?) У меня уже есть общепринятые определения.

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

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

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

А если вы считаете, что ООП в Java/C# - это Сложна, то вы еще не видели какой-нибудь Swift, где можно в несколько простых шагов насочинять свой собственный синтаксис, а развитие языка достигло такого маразма, что в community пытаются пропихнуть идею запретить объявлять переменные без приписки контекста (фактически к какому потоку будут относиться эти переменные).

Я на Swift дважды в контестах телеги призовые места занимал.

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

зашел в комментарии, чтобы увидеть хоть кого-то, кто удивится "а kotlin-то тут при чём".

не нашёл. озадачен. как раз в kotlin классическое ооп гнобится весьма усиленно, поддерживается больше ради интеропа.

Со своей колокольни хочу заметить небольшой момент. В любом посте про ФП против ООП адепты постоянно упарываются только в пару возможных применений программирования, отметая иные. Игровая индустрия тоже вот существует, например. Если вы считаете, что ООП ненужон, то очень хочется увидеть пример хотя бы одной коммерческой игры, написанной в парадигме ФП :)

У каждого инструмента своё применение. ФП, насколько понимаю, про изменение данных без изменение стейта. Ну-ну, игры это почти исключительно про изменение стейтов и взаимозависимости стейтов. Без ООПшных абстракций будет оч грустно.

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

Авторы движков за ООП в движке вас сожгут на костре из P4 Prescott. Там топят за data-oriented design (который ООП не то что ортгонален, а даже слегка противоречит), и прочие подобные вещи, которые как раз на ФП ложатся отлично.

Вот характерный доклад.

Не везде и не всегда, dod работает быстро и полезно для массовой обработки, но плох для узких вещей где нужна больше выразительность, большинство компаний используют либо смесь либо ооп подход. Хотя ecs конечно крутая тема.

А о чем спор. Ну, посыл то явно правильный, ооп не идеально, и зачастую переусложненно. И? Везде теперь фп корячить? Странный вывод. Функциональщина требует к себе более внимательного отношения при разработке, и в тч более высокой квалификации разработчика, в простых проектах, на 2-3 команды, либо в микропердисах она даже может быть частично применима, но при крупных монолитных решениях, которые еще в войны доткомов строились, такой подход делает лапшу на лапше. И вводилось ооп по строгим правилам линторам и тд именно для снижения цены часа разработчика, потому что типового простого тупого кода надо много, а 80%минусов описаных в статье давно уже порешались с помощью кодогенерации, которая еще удешевляет разработку. (Я кстати ни одного варианта кодогенерации для фп не видел:/). Пс. В го отличное переосмысление обоих подходов, но тоже много своих косяков. ППС. Штамповка тоже кучу косяков и сложностей имеет, но в отличие от выточки сильно дешевле и быстрее.

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

Скрытый текст
Жюри оценивающее насколько плох код
Жюри оценивающее насколько плох код

С обязательным автоформатированием. Никакое ООП не победит однострочник заканчивающийся сотней скобочек.

Я вас умоляю, это только у питонистов не получится. А вот если в языке есть точка с запятой, кто мешает все бабахнуть в одну строку?

<?php class Greeting {public function printGreeting(): void {print 'Hello World';}}; (new Greeting())->printGreeting(); ?>

Эй, в питоне есть точка с запятой вообще-то!

Просто она необязательна.

Хотя да, формтирование не поможет, eval же есть.

Я не настоящий сварщик, просто мимо проходил.

Но смотрю, тут много экспертов в комментах собрались.

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

Если википедию посмотреть и нейронок спросить - вроде как может, но это неточно.

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

Рекурсивные запросы на SQL вполне себе обыденность, когда строишь дерево по parent/child

SQL - декларативный

Большинство операторов SQL (в том числе SELECT, INSERT, UPDATE, DELETE) имеют вполне операциональный смысл. То, что мы не знаем, как именно в точности они будут выполняться, не делает SQL декларативным языком. Это последовательные операции с конкретным результатом каждой из них. А так-то и для C++ невозможно назвать точную реализацию каждого оператора в машинном коде.

Кроме того, вообще на самом деле нет прямого противопоставления между функциональными и декларативными языками (хотя такая точка зрения и встречается). Функциональную запись по самой её природе часто можно понимать на выбор в двух аспектах – как денотационную семантику (утверждение о тождестве некоторых функций) или как операционную семантику (алгоритм вычисления). И в своём денотационном аспекте функциональные языки имеют декларативный характер.

Оракл разделяет SQL на разные языки. Как правильно отметили в коментах, язык запростов декларативный. В указываете что вы хотите получить.
https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/Types-of-SQL-Statements.html#GUID-E1749EF5-2264-44DF-99EF-AEBEB943BED6

Есть отдельный язык для написания процедур https://ru.wikipedia.org/wiki/PL/SQL.

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

Автор молод в душе и оперирует максимумами (хотя может стажа приписал как иногда советуют на Хабре): Есть только один путь, а все останое неправильно.

Многие аргументы против ООП просто слабые. Вопрос выбора инструмента для конкретной ситуации опущен.

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

О! Родственная душа! Впервые за годы программирования! ООП не нужен! Он вреден! Бессмысленная когнитивная нагрузка на ровном месте!

И снова веб-разработчик на джаваскрипте. Прям тенденция.

Джаваскрипт это прекрасно!

На фоне непрекращающихся статей с жалобами о том, какое бесконечное зло для программирования ООП и статическая типизация, не хватает, хммм... Чего-то позитивного и конструктивного. А именно "Спагетти-код, BLOP (Broken Logic Oriented Programming) и CCLOD (Can't Care Less Oriented Design): лучшее, что случалось с программированием".

Ничего на свете лучше нету
Чем пилить программу до рассвета
Наши баги мучают дебаггер
А профайлер ловит наши лаги

... Эхх, профайлер любит наши лаги!

Мы своё призванье не забудем
Говнокод за деньги дарим людям
А паттернов гаденькая мода
Не заменит нам спагетти-кода

... Не заменит нам спагетти-кода!!1

/s

P.S. Сарказм сарказмом, но есть такое персональное впечатление, что чем тяжелее случаи спагетти, BLOP и CCLOD, тем больше бизнесы готовы платить. А продукты, созданные таким путём - те, которые реально кормят бизнес - это, без преувеличения, шедевры вершин человеческого гения. Правда, в них всё настолько гениально, что сами гении быстро забывают как оно работает в деталях (и начинается болезненная и опасная для развивающегося бизнеса эпоха "работает - не трожь!"). А моменты, когда сквозь отчаянную адреналиновую отладку внезапно понимаешь, что вот в этом месте многотысячестрочного полотна корректный результат обеспечивается некорректной логикой, а потом находишь, где это поведение гарантируется наличием и игнорированием ошибок прямо вопреки тому, что код пытается рассказать - получаешь непередаваемо сильные впечатления и по ходу открытий, и, в кульминации (когда уже два раза думал, что ещё интереснее и адреналиновее быть в принципе не может, но через пару часов происходит третий раз), и по ходу распутывания этого адского клубка счастья. Серьёзно, в такой день чувствуешь себя как на американских горках. Хотя, это, конечно, бледное сравнение - там далеко не так захватывающе, да и заканчивается быстро.

жалобами о том, какое бесконечное зло для программирования ООП и статическая типизация

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

Меня запишите.

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

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

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

Хорошо Вам живётся на свете, карма у Вас благая. Я после 20 лет активного программирования следующие 10 лет вообще не мог смотреть на все вот эти вот циклы, классы, присваивания и прочую шнягу, где открываешь код – и сразу сверху уже понятно, что будет на следующих 20 экранах. Потом, вроде, немного попустило, но уже в основном только в извращённых формах торкает. А вот эти все сеттеры-геттеры – это просто по́шло.

вот эти все сеттеры-геттеры – это просто по́шло

А и всё равно лучшего места для coercion/validation — мир пока не придумал :)

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

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

Так что это не карма, это богатый жизненный опыт :) Меня бесит только кот, проголодавшийся в четыре утра, да и то…

Кстати, про кобол. Подумалось, что вульгарное ООП - это кобол нашего времени. Неизящно, избыточно и многословно, но деньги приносит.

В каком месте кобол неизящный, я уж молчу про «многословный»?

Гнать на контр-адмирала Грейс Хоппер — все равно, что обвинять Пушкина в вялости рифм и простоте слога Онегина.

В текущий период жизни я думаю, что Бог дал нам для безмерного восхищения своим творением три вещи: S-выражения, форму COND и рекурсию. Особенно форму COND. Меня прямо прёт каждый раз, когда я пишу COND, и вдвойне – когда в сокращённой форме веток или со скимовской стрелочкой =>, то есть с двойным использованием условия.

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

Про S-выражения и рекурсию — полнейший консенсус. А кто такая «форма COND»?

Ну и это, когда кобол создавался — Бог еще только учился горшки обжигать, все-таки.

COND – это условное выражение в Лиспе и производных от него языках. Чем оно удобнее обычного условного оператора:

  1. Это выражение.

  2. Веток может быть сколько угодно.

  3. Условие после проверки может быть при необходимости либо само же возвращено в качестве результата, либо передано для обработки в ветку результата (так как Лисп – слабо типизированный язык, и в качестве истинного условия может фигурировать любое значение, отличное от лжи).

До некоторой степени это похоже на вертикальную палочку в Хаскеле.

Пример на C++:

int result;
auto x = expression;
if (x != nullptr) 
  result = f (x);
else
  result = 0;
return result;

Пример на Scheme:

(cond
  (expression => f)
  (else 0))

А, с этим COND я знаком :)

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

Как он может быть медленнее паттерн-матчинга (причём заведомо), если это просто ветвление?

LHO является выражением (expression), а значит — вычисляется.

Поясните, пожалуйста, свою мысль. Я как раз пишу о том, что в удачном случае у COND по сути нет RHO.

Э-э-э-э… Я потерял нить.

Как нет RHO когда в вашем же примере вычисляется f(expression)? Кроме того ну не всегда же такой фарт, что LHO прямо как есть передается в RHO. Если нет — то вычисляется сначала LHO, потом RHO.

Кроме того, паттерн-матчинг даже в этом примере выглядит точно так же (пример на эликсире):

case expression do
  nil -> 0
  x -> f(x)
end

В общем, это чистая вкусовщина, как мне кажется.

Ну конечно, когда можно применить case, то надо применять case. Тут нет никакой почвы для спора. Такая же форма case есть и в Scheme.

Но жизнь же гораздо богаче переключений по веткам одного expression. Вот, например, из рабочего кода:

(define (bad-dictionary? l)
  (cond
   ((not (list? l)))
   ((null? l) #f)
   ((not (list? (car l))))
   ((not (= (length (car l)) 2)))
   ((not (symbol? (caar l))))
   (else (bad-dictionary? (cdr l)))))

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

case expression do nil -> 0 x -> f(x)end

В вашем примере значение x вычисляется два раза?

Понял Ваш пример. На Scheme можно записать так:

(case expression
  ((#f) 0)
  (else => f))

В общем да, дело вкуса.

Класс. А где можно прочесть про => с примерами и пр? Это для любой Scheme работает? В частности, интересует быв. PLT Scheme.

Это стандартный синтаксис cond и case в Scheme R7RS (а вроде и начиная с R5RS). Думаю, что должно работать и в PLT. Означает в случае cond, что результат вычисления условия слева подставляется в качестве единственного аргумента лямбды справа, а в случае case – это фактическое значение переключателя в ветке else, которое точно так же подставляется в лямбду. Это просто из описания cond и case.

Классический пример такой:

(cond
  ((assoc key list) => cdr)
  ...)

Спасибо, то есть, надо хорошо понимать, по каким значениям срабатывает условие в cond... Ну если это не банальные #t, #f.

А cond работает просто, #f (или nil в Лиспе) воспринимает как ложное условие, а что угодно другое – как истинное условие.

Поэтому assoc нам возвращает либо пару из ключа и записи, либо #f. И если это не #f, то мы берём саму запись при помощи cdr.

Ещё раз спасибо!

То, что вы рассказали на протяжении этих комментариев — такие тонкости, которые отличают мимокрокодила от человека, думающего на языке. Их, к сожалению, можно получить только погрузившись в тусовку. :-(

Меня прямо прёт каждый раз, когда я пишу COND, и вдвойне – когда в сокращённой форме веток или со скимовской стрелочкой =>, то есть с двойным использованием условия.

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

let f (a:int option) (b:(int, float) Either.t) =
   match (a, b) with
   | None, Left 8 -> ...
   | Some 3, Left x when x < 0 -> ...

Блин, почему только Ракетчики догадались определить троеточие как undefined?

Ну, в Lisp и Scheme-то ничто не мешает самому определить. Что-нибудь вроде:

(define ... #!undefined)

Конечно, всё равно на уровне конкретной реализации, потому что стандарт Scheme, к большому сожалению, не определяет семантику неопределённых значений:

Так сразу не найду, но где-то в комментариях к другой статье хейтилось сразу всё - типы, ООП, ФОП. Или это уже у меня в голове смешалось... В последнее время подобные статьи выходят часто - сезон, наверное.

Отличная идея, кстати, для заметки: не ассемблер — не мужик.

Я писал на ассемблере. В универе. И вообще написал скрипт на Питоне, который генерит ассемблер, потому что было лень указатели на начало строчки текста с сообщениями руками проставлять.

Вывод: мужик.

Вы точно не путаете ООП с плохим кодом? То, что вы описываете — это не недостатки парадигмы, а последствия неправильного использования. "Методы прибиты к классам"? Так используйте интерфейсы и dependency injection. "Наследование — антипаттерн"? Ну так композицию кто отменял? Всё, что вы критикуете, давно решено паттернами вроде Strategy или Decorator. Вы хоть раз видели нормальный код на C#, где DI-контейнеры управляют жизненным циклом? Или вы до сих пор пишете new в каждом втором методе? Может, проблема не в ООП?))

Комментарии, как всегда, лучше самой статьи. Я, конечно, ненастоящий сварщик (три года на PowerBuilder'е, евпочя, были очень давно), но создаётся ощущение, что автор за эти 13 лет еле-еле выполз на нижний уровень мидла, попутно не осилив даже понимания фразы "каждому овощу - свой фрукт".

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

Простейший пример, когда за счёт ООП можно очень хорошо сэкономить время:

Есть некая конфигурация некого процесса, сохранённая в словаре и в нескольких дополнительных переменных. Есть коннэкшн к БД. Практически все функции, представленные в данном процессе, используют подключение к БД. Во многих функциях дёргается конфигурация.
Вопрос к автору: Вы правда собираетесь КАЖДОЙ функции передавать в качестве аргументов инстанс коннэкшна к БД? Или собираетесь следовать антипаттерну использования глобальных переменных? Да, можно весь конфиг объединить в структуру. В C без этого не обойтись. Но согласитесь, с ООП это банально удобнее. Один раз передали всё в конструкторе -- используем сколько угодно раз.

Это самый базовый пример. Давайте пробежимся по всем аргументам автора:

1) Собеседование. Давайте будем правдивы: то, что встречается на собеседовании, в жизни встречается очень редко. На собесах даются экзотические примеры на знание базовых особенностей языка и на знание основных алгоритмов. Поскольку в мире существует море библиотек, и поначалу мы пишем не самый сложный код, мы достаточно редко применяем эти знания. Поэтому, аргумент, который приведён самым первым, я считаю некорректным.

2) Из моего примера: допустим, у нас есть множество методов, подобных getDisplayName. Пусть это будет getFirstName, getLastName, getAbbr, getMiddleName, etc. Согласитесь, удобнее один раз создать объект, и далее вызывать у него методы, не передавая в них ничего. Но. Это не самый весомый контр-аргумент. Посмотрите на реализацию 1-й функции. Что вы видите? if (!user) return undefined. Ничего не смущает? Мы неоправданно используем совершенно лишний if. И это не всегда можно исправить статической типизацией: например, при использовании pointer мы должны проверять, не является ли он nullptr. Во втором примере мы просто должны передавать все параметры, что ОЧЕНЬ усложняет жизнь. Мы должны знать абсолютно все данные, хранящиеся в структуре, чтобы корректно это передать.
Ошибку null pointer exception в данном контексте считаю неуместной. Уж лучше мы ОДИН раз проверим на null объект ВНЕ функции, чем КАЖДЫЙ раз будем проверять это внутри

3) Переопределение методов -- действительно добавляет возможностей выстрелить себе в ногу даже при большом опыте использования ООП. Банально по невнимательности. Но тем не менее это помогает избегать дополнительных проверок. В функциональном переопределении мы будем использовать if/switch. Это минус производительность. В ООП полиморфизм происходит как бы "неявно" засчёт как раз таки наследования

4) Наследование. Очевидно, не бывает универсальных решений. Для исправления описанных проблем есть соответствующие паттерны. Автор называет паттерны "костылями", но я бы назвал эти решения просто... решениями. В них нет ничего, бьющего по производительности и удобству

5) Полиморфизм: см. пункт 5

6) Шаблоны/паттерны: в жизни довольно редко используется большое количество шаблонов и паттернов. Существуют основы основ. Никто не говорил, что ООП -- просто)) Разумеется, есть свои сложности. Но тем не менее, это даёт множество бонусов. Никто не будет просто так создавать фабрики в обязательном порядке на все классы или BuilderOfBuilderOfBuilder. Нет. Это делается только при необходимости. Применение любого паттерна должно быть обосновано. Синглтон, как и многие другие паттерны, насколько я знаю, в последнее время не особо приветствуется. И вообще, чрезмерное использование паттернов влечёт за собой множество проблем. Всё зависит от задачи, всегда помните это

7) конструкторы: отчасти соглашусь, отчасти нет. Здесь всё зависит от задачи. Как впрочем, и в остальных случаях

8) Могу привести в пример прекрасную реализацию dependency injection в роутинге Laravel (попрошу не материться по этому поводу: PHP -- первый язык, с которого я начал свой долгий путь в IT)

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

10) в примере ООП, на мой взгляд, изначально не очень правильная архитектура. Логичнее было бы вынести логику обновления из класса User. Или же сделать что-то вроде метода save, который не вызывает метод класса, в котором хранится этот самый объект

11) несколько моделей. не вижу проблем

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

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

Итог: всё зависит от конкретных задач)

Вы правда собираетесь КАЖДОЙ функции передавать в качестве аргументов инстанс коннэкшна к БД? 

Хоть я не автор, но отвечу. Особенность реализаций того, что сейчас принято называть ООП, в том, что по сути именно это и происходит - КАЖДОМУ методу просто в неявном виде передаётся инстанс объекта в качестве нулевого аргумента.

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

Обоснуйте. Чем неявное лучше явного?

Я-то это уже обосновал тут. Это вы попробуйте обосновать, зачем вываливать класс кишками наружу.

У вас очень необычная манера аргументировать. Вы даёте ссылки на большие документы и ожидаете. Я как-то раз прочитал один такой. Матерьяльчик был сомнительный да и ответов на свой вопрос я там не нашёл. Здесь прямо на весь солид ссылка, да еще и на незнакомом мне сайте.

Потому что "явное лучше неявного" (см. пункт 2). Этому принципу более 25 лет, если что. Если он прошёл мимо вас - соболезную.

Ну так-то помимо питона есть и другие языки, парадигмы, идеологии. Например, в мире полно́ адептов концепции «convention over configuration», которую приписывают, вроде, DHH — и нет ни единого аргумента кроме вкусовщины за то, что ваш дзен лучше нашего кунфу.

Т.е., вы сейчас на полном серьёзе утверждаете, что принципы, на которых построен один из самых популярных на данный момент ЯП, по весу аргументации равноценны принципам, изложенным на одном из интернет-сайтов? Я восхищён и соглашаюсь с вами.

Самое популярное — почти никогда не лучшее. И питон — ярчайшее тому подтверждение.

Утверждать, что питон якобы построен на этих принципах — все равно, что до сих пор отождествлять гугл с их ранним девизом «don’t be evil».

Ну и правила написанные в PEP, — вообще никак не релевантны для всего остального мира. Даже уголовный кодекс действует только в пределах одной страны.

Я с вами уже согласился и соглашусь ещё раз.

Не делай себе кумира и никакого изображения того, что на небе вверху, и что на земле внизу, и что в воде ниже земли; не поклоняйся им и не служи им, ибо Я Господь, Бог твой, Бог ревнитель, наказывающий детей за вину отцов до третьего и четвертого рода, ненавидящих Меня, и творящий милость до тысячи родов любящим Меня и соблюдающим заповеди Мои.

(см. пункт 2) Этому принципу более 2 тысяч лет, если что. Если он прошёл мимо вас - соболезную.

Браво! В Святом Писании вы разбираетесь явно лучше, чем в современной теории разработки ПО. Я пишу на JS потому, что разрабы Пайтона сами же нарушили свой принцип №13, создав многолетний конфликт между версиями 2.7 & 3.x. Так что я сам выбираю принципы, которым "служить и поклоняться", просто в силу того, что я - человек, свободный в своём выборе. И как человек, достаточно давно практикующийся в этом, могу сказать, что как проповедник вы не очень.

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

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

Вы либо крестик снимите (и прекращайте нести чушь), либо штаны наденьте (и переходите на WASM).

В хороших фреймворках IoC реализуют не через инъекцию, а неявно через контекст окружения.

Вот это - чушь. Я вам уже указывал на то, что Ambient Context (контекст окружения) является анти-паттерном для IoC и приводил ссылку на книгу "Dependency Injection Principles, Practices, and Patterns" от Steven van Deursen and Mark Seemann, с которой сталкивался каждый, кто хоть немного углублялся в принципы IoC. Там написано почему это так - см. пункт 5.3.

А вы продолжаете нести чушь в массы про "неявное лучше явного".

Контекст окружения имеет право на существование, но только когда его самого внедряют в объект (через конструктор или метод):

Ambient Context должен быть использован только в редчайших случаях. Для большинства случаев больше подходят внедрение в конструктор или внедрение в свойство, но у вас может быть реальный Cross-Cutting Concern, который загрязняет каждый API в вашем приложении, если вам нужно передать его всем сервисам.

А в вашем фреймворке вы загрязняете globals, используя его в качестве контекста и размещая в нём "вообще всё". Достаточно нажать F12 (DevTools) и запустить вот этот код на вашем любимом page.hyoo.ru, куда вы всех обычно посылаете:

(()=>{debugger})()

Вот это всё ваши объекты в глобальной области видимости (с префиксом $hyoo_ и $mol_):

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

Не, ну лично вы, этот зоопарк сами писали и в нём разберётесь, если что крашнется, а человеку со стороны как в этих неявных зависимостях копаться?

Тупой вопрос: как вы мокать глобальное окружение в тестах собираетесь? Каждый раз пересобирая его, для каждого теста? Как это отразится на скорости тестов? Или каждый-всякий-любой может в глобальном окружение подменить объект моком? И на проде тоже?

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

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

Там написано почему это так — см. пункт 5.3.

Argumentum ad verecundiam. Не существует книг, в которых написана истина. Оратор выше своей цитатой из библии вам намекал именно на это.

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

Так что не нужно своих идолов выдавать за истину в первой инстанции.

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

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

Я довольно плотно использовал IoC (DI в частности) и в Java, и в PHP. Сейчас использую в JS. Извините, не смог удержаться от комментария, когда увидел такой бред :)

Я не хвалюсь эрудицией, я слегка туповат, чтобы свободно владеть несколькими языками программирования одновременно. Сейчас я программирую на JS и уже почти забыл PHP и довольно сильно забыл Java. А уж всё, что вы сказали про хаскель, эрланг, атомы и всё остальное, для меня вообще "белый шум". Может с вами кто-то и подискутирет на эту тему, но не я :)

Я могу только про DI немножко, я там хоть что-то понимаю. По крайней мере, если бы я увидел обоснование, чем Ambient Context лучше Constructor Dependency Injection - я бы понял, что это оно, то самое обоснование.

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

Если вкратце — огромное количество ненужного бойлерплейта там, где он не нужен.

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

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

Вы же видели вот этот скрин, да? Это та самая "комната, где нет ненужного бойлерплейта". И если среди этих объектов есть объекты с транзиентными зависимостями - удачи вам в дебаге при рефакторинге!!!

Я вам просто приведу свой фрагмент кода, где в явном виде прописаны все зависимости в конструкторе:

    /**
     * @param {Fl64_Otp_Back_Defaults} DEF
     * @param {TeqFw_Core_Shared_Api_Logger} logger
     * @param {TeqFw_Db_Back_App_TrxWrapper} trxWrapper
     * @param {Fl64_Otp_Back_Store_RDb_Repo_Token} repoToken
     */
    constructor(
        {
            Fl64_Otp_Back_Defaults$: DEF,
            TeqFw_Core_Shared_Api_Logger$$: logger,
            TeqFw_Db_Back_App_TrxWrapper$: trxWrapper,
            Fl64_Otp_Back_Store_RDb_Repo_Token$: repoToken,
        }
    ) {}

Вот это - гарантия того, что мой объект работает только с этими зависимостями. Вот это - комната с минимум мебели, по которой ты ходишь с открытыми глазами. И этот объект будет работать в любом окружении, если передать ему в конструктор нужные зависимости, сделанные хоть контейнером, хоть вручную. Да даже в консоли браузера можно его инициировать. Любыми объектами - моками и имплементациями. Вот это - технология, проверенная десятилетиями и в разных ЯП.

к каким-то книжкам, написанным хрен пойми кем 

Ну, так-то там и имена, и фамили указаны. Можете погуглить, кто эти "хрен пойми кто". Тиражи книжки найти можно. Критику изложенных в этих книжках идей. Ответы авторов на критику.

Я, например, сайтов наделал довольно много и в любой момент могу наделать ещё. Могу сделать сайт, на котором могу написать, что "яйцо правильно разбивать с тупого конца", а через дорогу сделать сайт, где напишу, что "яйцо нужно разбивать с острого конца". А потом редиректить вас на тот или иной сайт по настроению со словами - "Вот же! В интернете написано!!".

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

Я нигде и никогда не утверждал, что код со скрина — эталон. Но пример (или несколько примеров) говнокода не могут ничего подтвердить (или опровергнуть).

Более того, я, в принципе, согласен с тезисом «явное лучше неявного». Я несогласен с подачей: через апелляцию к авторитету, ссылки на книжки и примеры кода, который вообще нерелевантен обсуждаемому вопросу.

Что будет, если вам в конструктор придется передать еще один параметр в связи с эволюцией бизнеса? В лучшем случае — если это ваш внутрипроектный код — придется найти все вызовы и обновить их. А если это библиотека общего назначения? — Тут неприятная дилемма: либо ломать обратную совместимость (что в моём мире равноценно самоубийству, сообщество больше никогда не станет пользоваться моими библиотеками), либо придется сделать фабрику и скопипастить кучу кода. Оба варианта — так себе.

Именно для решения этой проблемы во всех функциях Win32API в своё время был аргумент **lpRESERVED — но это ведь тоже не фонтан.

слово книжное имеет вес чуть больше слова с сайта

А для меня слово разумное имеет вес чуть больше слова неразумного. Источник тут вообще ни при чем.

Но пример (или несколько примеров) говнокода не могут ничего подтвердить (или опровергнуть).

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

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

Вы не можете утверждать авторитетность или релевантность источника, пока не ознакомитесь с содержимым. А вы с содержимым не ознакомились и сразу отвергли источник, как неавторитетный. Я дал вам возможность ознакомиться с идеей, ссылаясь на источник, в котором она изложена достаточно полно и достаточно ясно. Лошадь можно привести к водопою, но её нельзя заставить пить, если она не хочет.

Что будет, если вам в конструктор придется передать еще один параметр в связи с эволюцией бизнеса?

Это JS и фактически параметр конструктора там всего один (см. Unpacking properties from objects passed as a function parameter). Внутри него вы можете передавать хоть десяток вложенных параметров, хоть сотню. Сигнатура конструктора не изменится. Я могу добавлять или удалять параметры внутри объекта в конструкторе вызываемого кода и это никаким образом не повлияет на вызывающий код. Вообще никаким.

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

А для меня слово разумное имеет вес чуть больше слова неразумного. Источник тут вообще ни при чем.

Для меня так-то тоже. Но вы же в источник даже не заглянули. Поэтому и разумность слова оценить не можете.

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

BTW, я заглянул в гитхаб Steven van Deursen, и не увидел там ничего такого, что побудило бы меня прочитать хотя бы абзац его книги.

Вы великолепны!!! Вам предлагают оценить идею и дают ссылку на её изложение, а вы копаете репутацию автора на гитхабе и при этом на серьёзных щах утверждаете "Источник тут вообще ни при чем"?! Так вам авторитетность источника важна или только разумность самой идеи? Давно я так не веселился. Спасибо!

С чего вы взяли, что идея мне незнакома? Вы бы мне еще с идеей DDD предложили ознакомиться.

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

Ладно, тогда вы обоснуйте чем неявное лучше явного вот в этом контексте:

В хороших фреймворках IoC реализуют не через инъекцию, а неявно через контекст окружения.

Я уверен, что вы относите себя к тому типу людей, "которые способны писать код и подтверждают это". Уверен, ваше обоснование с интересом прочтёт сам Карловский.

Только, пожалуйста, не сливайтесь, как он.

Всё, я убежал - у меня Масленица!! Завтра почитаю ваши формулировки.

У вас какие-то беды с логикой. В этом контексте — не знаю, мне этот контекст не слишком-то интересен.

Но я ведь нигде не утверждал, что-либо про этот контекст, правда?

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

Но даже в этом контексте я уже два раза в этом треде упоминал рельсы (Ruby on Rails). На рельсах есть куча счастливых разработчиков, которые пишут проекты типа Twitter (да, потом его зачем-то переписали на Скале, но это просто потому, что кому-то вожжа под хвост попала).

Ну, раз вы не знаете предмет, который мы тут с коллегой @nin-jin обсуждаем, то и разговаривать с вами в этом треде более не о чем. В Святом Писании вы явно лучше разбираетесь.

Эх, а я вам ещё пример кода приводил! Да вы же в нём не видите ничего! Для вас, что внедрение в конструктор, что контекст окружения - всё едино :(

Я нигде и никогда не утверждал, что код со скрина — эталон. Но пример (или несколько примеров) говнокода не могут ничего подтвердить (или опровергнуть).

Вы даже не поняли, что на скрине не код :(

Вот теперь думаю, зачем вы в профиль Steven van Deursen на гитхаб заходили. Что вы там увидеть собирались?

Я заранее согласен со всем тем, что вы скажете или подумаете.

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

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

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

Мой фреймворк, в качестве дефолтного контекста окружения использует нативный глобальный контекст (globalThis). Это не обязательно, но это весьма удобно, ибо позволяет создавать контексты, где переопределены в том числе и нативные глобальные значения.

Также это предоставляет простой и удобный доступ к ко всем рантайм объектам из консоли, без необходимости шаманить с debugger в IIFE. Это одна из множества фишек для удобства дебага и рефакторинга, о которых вы даже не мечтали, а у нас уже 10 лет как есть.

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

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

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

В частности, освойте уже TypeScript. Бойлерплейт на JS без тайпчека в 2025 - это какой-то детский сад уже.

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

Нелепо они выглядят для вас и ваших адептов. Для тех же, кто не читал вашу статью, они не более нелепы, чем любые другие аргументы. А я ориентируюсь на массовую аудиторию.

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

Я просто использовал вашу же терминологию "неявно через контекст окружения". Я не знаю, как у вас реализован ваш "контекст окружения", но каждый может увидеть результат этой реализации в global scope.

Я не утверждал, что "неявное лучше явного"

Верно, вы лишь утверждали что обосновали это:

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

Вообще-то globalThis является глобальным объектом. Каждый может вбить в браузере:

console.log(globalThis)

и убедиться, что мы чертовски близко подошли к опасной зоне "упоротости в борьбе с соломенным чучелом". Всё,что нас удерживает на грани, так это то, что globalThis не является переменной :)

Также это предоставляет простой и удобный доступ к ко всем рантайм объектам из консоли, без необходимости шаманить с debugger в IIFE.

Согласен, можно было и просто вбить на page.hyoo.ru в консоли браузера

this

и увидеть то же самое:

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

Это вы сейчас про что, про консоль в браузере? Или про то, что через неё все "кишки" можно увидеть? Ну так это не ваша заслуга, а разработчиков браузеров.

К тому же вот этот простой код, введённый с консоли или любым сторонним кодом, убивает функциональность вашего фреймворка:

this.$mol_delegate = 32;

Проверить очень просто, достаточно войти на page.hyoo.ru, вбить в консоль эту строку и затем попытаться использовать поиск по сайту в правом верхнем углу:

В частности, освойте уже TypeScript. Бойлерплейт на JS без тайпчека в 2025 - это какой-то детский сад уже.

Мне пока что и в детском саду неплохо - свободы больше.

Имена хороших в студию и небольшое описание как в них всё работает.

Мне нравится как сделано в Spring. Есть глобальный контекст со всем модулями. Когда вы пишите модуль вы явно указываете зависимости которые нужно взять из глобального контекста.

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

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

Ответы на все ваши вопросы есть по ссылке. Там же вы узнаете, почему у нас значение из контекста не может быть "пустым" by design.

Еще лучше их выявлять при компиляции, а не при старте!

Если говорить про инверсию зависимостей, то это не возможно.

Приложение при старте читает конфигурацию и решает какие имплементации инджектить в приложение. Это точно не про компиляцию.

Это точно не про компиляцию.

Чего? Вот библиотека, которая осуществляет экспорт сразу в несколько внешних источников. В prod среде она соберёт только тот код, который заявлен как требуемый (не подключили явно кликхаус — кода для него в продакшене не будет), а в test — соберет моки и стабы.

И вот эти вот самые паблишеры — на станции компиляции — инжектятся в исполняемый код (в соответствии с конфигами).

Всячески согласен! В начале 90-х я работал преподом на C, а затем (вынужденно по необходимости) на C++, и ответственно заявляю:
Три проблемы ООП - это наследование, инскапсуляция и полиморфизм. Вот без них всё отлично. А классы можно оставить, только методы оттуда выкинуть и назвать структурами.
Ой, снова C получился! И он идеален.

И он идеален

Идеален как коммунизм: в фантазиях всё супер, в реальности куча ошибок и дыр.

Не знаю как в вашей реальности, а в нашей школьной реальности 1992 года сортировку пузырьком, поиск подстроки и поворот массива было проще и надежней делать на C :) . А другие задачи мы и не решали.
p.s. Это был сарказм.

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

альтернативу которому сложно придумать

Модель акторов гораздо лучше для проектирования систем подходит. И придумали лет 40 назад уж. Хотя в те времена - это и называлось ООП)

В те времена это не называлось ООП. Но на самом деле под ООП можно подогнать любую модель, настолько объектный подход органичен окружающему нас миру. Вам молодым просто припудривают сознание, а вы и уши развесили: "придумали лет 40 назад уж ". На самом деле не 40, а на 10 раньше - в середине 70-х, что для нашей отрасли равносильно смене поколений.

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

pipe-оператором, чем же ещё...

Позволяет передавать данные от функции к функции, и концептуально хорошо знаком всем, кто работает в консоли. Elixir и F# передают привет всем, кто ещё не додумался

Об этом додумались давно. К примеру см. принцип действия std::cout с оператором <<

само понятие объекта пришло в computer science из философии, означает нечто существующее вне нас, см. объект-субъект, это понятие достаточно сложное, т.к. зависит от критерия существования, но здесь не про философию,

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

полностью согласен с автором в том, что "надо писать качественный max простой код", также вполне понимаю, если в тех проектах, что он делал ООП был неуместен, ну и отлично не используйте, но к чему обобщения на те области программирования, где у Вас нет опыта?

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

А теперь представьте два класса: «мартышка» и абстрактный «фрукт». От фрукта уже наследуем и банан, и что душе угодно, а мартышку даже изменять не надо, потому что она держит абстрактный фрукт. Можно ли такого добиться в условном C? Можно, но будет ли это также красиво, как в C++?

Тот, кто делал класс "мартышка и банан", ничего не знает ни о вас, ни о ваших проблемах. У него были свои задачи. Он реализовал мартышку и снабдил её едой на первое время, и знать не знал, что вы захотите отнаследоваться от мартышки ради банана.

Так а почему это минус ООП-то даже при наличии класса «мартышка и банан»? Потому что в ООП-языках нет фичи с исключением полей? Ее нет, потому что это очевидный костыль, созданный решать проблемы неправильной иерархии наследования. Ну убрали вы из структуры ненужные поля, но функции-то продолжают ожидать эти поля. Какой выход? Копипастить функции, написанные для «мартышки и банана», и удалять оттуда поля мартышки? Этот костыль не решает проблемы, созданные кривым архитектором, неважно, ООП это или ФП. Однако, если сравнивать корректную иерархию и для ООП, и для ФП, очевидно, ООП выигрывает благодаря своему интуитивному структурированию кода, который жизненно необходим в больших проектах.

попросил chat gpt проиллюстрировать, то что хочу донести.

хваленый C мартышка и банан
#include <stdio.h>
#include <stdlib.h>

/* Абстрактный фрукт */
typedef struct Fruit {
    void (*eat)(struct Fruit *);  // указатель на функцию "есть"
} Fruit;

/* Вызов метода eat */
void fruit_eat(Fruit *f) {
    if (f && f->eat)
        f->eat(f);
}

/* Определение класса Banana (банан) как наследника Fruit */
typedef struct Banana {
    Fruit base;           // базовая структура Fruit
    const char *color;    // дополнительные данные для банана
} Banana;

/* Реализация метода eat для банана */
void banana_eat(Fruit *f) {
    Banana *banana = (Banana *)f;
    printf("Едим банан цвета %s\n", banana->color);
}

/* Функция для создания банана */
Banana *create_banana(const char *color) {
    Banana *banana = malloc(sizeof(Banana));
    if (!banana) return NULL;
    banana->base.eat = banana_eat;
    banana->color = color;
    return banana;
}

/* Класс Monkey (мартышка), который держит указатель на абстрактный Fruit */
typedef struct Monkey {
    Fruit *fruit;
} Monkey;

/* Метод, инициирующий процесс поедания фрукта */
void monkey_eat_fruit(Monkey *monkey) {
    if (monkey && monkey->fruit)
        fruit_eat(monkey->fruit);
}

int main() {
    Monkey monkey;
    Banana *banana = create_banana("жёлтый");
    monkey.fruit = (Fruit *)banana;  // мартышка работает с абстрактным фруктом

    monkey_eat_fruit(&monkey);

    free(banana);
    return 0;
}

худший C++ мартышка и банан
#include <iostream>
#include <memory>

using namespace std;

/* Абстрактный класс Fruit */
class Fruit {
public:
    virtual void eat() = 0;  // чисто виртуальная функция
    virtual ~Fruit() {}
};

/* Класс Banana, наследник Fruit */
class Banana : public Fruit {
public:
    void eat() override {
        cout << "Едим банан." << endl;
    }
};

/* Класс Monkey, который содержит указатель на абстрактный класс Fruit */
class Monkey {
    unique_ptr<Fruit> fruit;
public:
    Monkey(unique_ptr<Fruit> f) : fruit(move(f)) {}
    void eatFruit() {
        if (fruit)
            fruit->eat();
    }
};

int main() {
    unique_ptr<Fruit> banana(new Banana());
    Monkey monkey(move(banana)); // мартышка получает абстрактный фрукт, без необходимости изменять свой код
    monkey.eatFruit();
    return 0;
}

вся статья - смесь какой-то глупой ненависти, гордыни и софистики. берем причины кривых рук, а ответственность кладем на ООП

Могу только посоветовать аффвтору ознакомиться с "Серебряной пули нет" Брукса

Брукс был умный и заслуженный человек, и про "Серебряную пулю" стоит почитать, но если поместить его достижения (в том числе по системе 360) в контекст, он сам и его начальник Thomas Watson Jr. (CEO IBM как и его отец) оценивали проект 360 как неудачный, отчасти поэтому Брукс покинул IBM в 1964, и закончил инженерную работу перейдя на преподавание в 33 года, другой проект IBM в котором Брукс участвовал - 7030 Stretch тоже был не слишком удачный

Проект 360 - это то, благодаря чему вычислительная техника вообще такая, какой мы её знаем. Брукс или Уотсон никогда не оценивали его как неудачный, а просто отмечали, что были значительно сорваны плановые сроки и стоимость. Но в результате до сих пор, уже 60 лет, IBM извлекает из этой системы прибыль.

Если бы проект 360 оказался неудачным, IBM бы обанкротилась, по словам Уотсона.

при всем уважении с Вами согласиться не могу, немного про детали состояния дел на момент ухода Брукса из IBM, источник легко найти в сети -

ps

возможно Watson говорил разное в зависимости когда и с кем

Вроде это никак не противоречит тому, что я написал.

дело не в противоречии, а в Вашей преувеличенной оценке значения IBM 360, это популярная точка зрения еще со времен СССР, если интересно найдите, что Dijkstra думал по этому поводу

Дейкстра работал в Burroughs, и вообще прославился тем, что всегда ругал всё, в чём не имел личного интереса, а уж тем более продукцию конкурирующей фирмы. Он был умный, конечно, мужик, но его оценочные суждения бессмысленно рассматривать, а тем более принимать на веру (как и Вирта).

А прославлял он архитектуру Burroughs (предшественник Эльбруса) и язык Алгол.

Про S/360 замечу, что прямо сейчас готовлю научную статью по семантике планирования вычислительных процессов, в которой первым пунктом в перечне литературы стоит описание S/360 (в контексте приоритета языка JCL). Вот насколько грандиозная это была вещь.

знаю достаточно про Burroughs, даже когда-то слегка разбирался в MCP, с Вами не согласен, спорить смысла не вижу, если Вы так думаете про Dijkstra дело Ваше конечно, но это ошибка

ps

если Вас это серьезно интересует, напишите в личку, например что знаете про Burroughs, можно обсудить, но без wiki

Если у Вас есть какая-то литература по WFL, я был бы признателен за ссылку.

насколько понимаю это про Unisys WFL, не уверен что это доступно в сети

Да, про него, изначально же это был Burroughs WFL. Жаль.

Не могу удержаться от замечания, что это отчасти подтверждает моё мнение. Вещи, за которые по коммерческой надобности топил Дейкстра, уже вычеркнуты из истории. Сравните с JCL. Не то, чтобы JCL или OS/360 в целом были красивыми и удобными вещами, но они оказались крайне успешны, так как удовлетворяли практические потребности того времени и отчасти опережали их. А Дейкстра смотрел на вещи как теоретик, причём ангажированный.

хотя Dijkstra не нуждается в защите, но не совсем понимаю почему Вы упомянули Wirth, консультантом Burroughs был Knuth, вместе с Dijkstra, его основным местом работы был Eindhoven,

про остальное, (imho) непонимание "кто есть кто", но с другой стороны, типа откуда Вам знать, почему данная документация недоступна,

вероятно, если бы у Вас была возможность общаться с людьми работавшими в Burroughs в то время, все встало бы на место, у меня такая возможность была

Я упомянул Вирта, потому что он был такой же желчный тип, нетерпимый к чужим точкам зрения. Читал, помню, статью Вирта в CACM, где он буквально изошёлся на известную субстанцию, критикуя Apple Macintosh, причём вы никогда не догадались бы за что – за метафору выкидывания дискеты в корзину для освобождения дисковода! На самом деле, видимо, просто будучи обиженным, что его собственная операционная система Oberon оказалась никому не нужна.

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

При этом как мыслители – Дейкстра и Вирт были выдающиеся люди. Я сам лет 10 зарабатывал на жизнь Паскалем (хоть это и был уже далеко не виртовский Паскаль, модифицированный против его желания).

типа откуда Вам знать, почему данная документация недоступна

Совершенно очевидно, что она недоступна, потому что не используется. В связи с тем, что, в свою очередь, фирма Burroughs пережила несколько банкротств и её продукция не используется тоже.

В отличие от JCL, который используется до сих пор, хотя и в небольшой нише.

Критерий истины – практика, и мне удивительно, как сейчас, с высоты прожитых лет, можно не расставить все точки над i в этой истории. Дейкстра мог быть прав или ошибаться в своё время, но мы-то точно знаем, чем всё в итоге закончилось.

да нет, как раз Вы знаете далеко не все, смотрите Вы не знали, что Knuth консультировал Burroughs, что широко известно, тем более не знаете истории проекта B5000, про Unisys примерно то же самое, Вы серьезно думаете, что все это есть в сети?

получается примерно как Вы видите айсберг, но не его подводную часть, даже если это уже далеко в прошлом типа 50-60е годы

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

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

И – возвращаясь к началу нашей беседы – мы можем твёрдо быть убеждены, что в СССР совершенно обоснованно скопировали архитектуру IBM, а не Burroughs. Потому что и СССР уже давно нет, а архитектура IBM – всё ещё вот она, в отличие от её тогдашних конкурентов. А то, что писал по этому поводу Дейкстра – просто его заблуждение, вызванное в том числе коммерческой ангажированностью.

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

Ваши убеждения понимаю, более того уважаю, но кое-что из упомянутого видел своими глазами, хотя к постановлению 1967 года отношения не имел, но как оно готовилось знаю достаточно, примерно когда Вы родились изучал листинги MCP, вообще интересовался предметом близким к Вашим трем статьям про "real time", и учился в аспирантуре,

с тех пор много воды утекло, любых споров на эту тему стараюсь избегать, но мнение есть, копирование системы 360 было ошибкой, одной из причин большого разрыва в уровне, который стал очевиден в 1980х

Я честно хочу понять, но не понимаю вашу позицию. Допустим, что машина Burroughs действительно была безмерно крута и концептуально превосходила S/360 во всём. Лично у меня есть сомнения, но я готов это принять в качестве гипотезы. И предположим, что СССР принял бы Burroughs в качестве прототипа, как предлагал Дейкстра. Думаю, не нуждается в отдельном обосновании, что от позиции СССР на Западе ничего бы не изменилось. И вот Burroughs бы обанкротился, а мы остались бы с неподдерживаемой архитектурой и с программами на Алголе, когда весь мир писал на Фортране. В чём вы видите смысл-то этого?

Допустим, в MCP было проще что-то написать, но ведь и комьюнити имеет огромное значение. А мировое комьюнити оказалось на стороне IBM.

В 1967 году выбор был непростой, но сейчас-то в ретроспектвиве ясно, что он оказался сделан единственно верным образом. А вы пишете так, как будто на дворе 1967 год и мы не знаем, чем всё на самом деле закончится.

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

про альтернативы лучшие чем копирование системы 360 - при ограниченном количестве ресурсов правильным было сделать приоритетным два направления - первое обеспечение научных расчетов, условно говоря направление CDC/Cray, например БЭСМ10 и т.д., и второе рабочие станции для CAD особенно микро электроники, направление DEC, Sun, Silicon Graphics, это должно было стать главным, также sw надо было делать свое с нуля, конечно архитектурно брать лучшее, но не копировать, а делать лучше, что было вполне возможно,

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

кое-что пытались корректировать позже, но время было потеряно, ресурсы истрачены на АСУ и прочее, к массовой разработке VLSI в начале 80х промышленность не была готова со всеми вытекающими последствиями, насколько знаю БЭСМ-6 так и осталась основной рабочей лошадкой 80х, в местах типа Арзамаса их стояло по 10 штук на этаже, лично не видел старших моделей ЕС, но по слухам работали не очень

Тогда не было DEC/Sun/Silicon, а стране было нужно АСУ. Условно говоря, по машине в каждый завод или дивизию.

К 1980-м годам, когда я работал на ЕС-1066, это был один из самых надёжных компьютеров вообще, которые я встречал за всю жизнь. Железо, конечно, потом научились делать лучше, но системный софт зато был безотказен, как АК.

ЕС-1061 - та глючно работала на уровне железа.

это интересно, именно про 1066, какая примерно средняя длительность между отказами?

смотрите Ваш год рождения 1974, если так Вам в 80х было 6-16 лет, верю что видели, но в качестве кого Вы работали на 1066?

стране был нужен АСУ

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

при том уровне вранья на бумаге который тогда был не понятно как эффективно АСУ могла работать, но внедряли конечно, и премии получали

Я с 13 лет программирую и в 15, то есть в 1989, начал зарабатывать этим деньги. К 1990 году я участвовал в разработке софта для оборонки, как раз на двухпроцессорном комплексе ЕС-1061/66 (потом меня за это без штатных экзаменов приняли в вуз, где я этим занимался). В основном это было на прекрасном языке PL/I, хотя было вокруг много людей, использовавших Фортран. Вычислительный комплекс работал круглосуточно, и раз в неделю, с утра по понедельникам, проводилась плановая профилактика. Таких случаев, чтобы он целиком отказал, я за несколько лет помню один раз. Так как я иногда оставлял свои программы считать в фоновом режиме по нескольку суток, то мог видеть, что и в моё отсутствие машина не останавливалась.

В начале 1990-х я побывал на ряде объектов в армии. Там у них в основном использовалась машина ЕС-1045 в военном исполнении, надёжностью которой военные тоже были довольны. Многие из этих машин проработали до 21 века. Больше всего в армии меня поразило, что их машины работают без гермозоны.

Что касается уровня вранья. Причём здесь вообще уровень вранья? АСУ - это конкретные расчёты. Заработная плата, наряд сил и средств, даже конструкция ракеты.

неплохо, типа бывший вундеркинд, допускал такое, но не был уверен, про АСУ предлагаю не спорить, для CAD другие требования, про обстановку конца 80х - этого уже не видел, т.к. к этому времени работал в DEC, вероятно Вам тоже там было бы интересно

Конечно, DEC – очень продвинутая фирма была. Я так и не понял, кстати, в какой момент они просчитались? Вы, наверное, можете это экспертно объяснить.

Я так и не понял, кстати, в какой момент они просчитались?

Когда отказались переходить на микрокомпьютеры. Десктоп — ключ к разработчикам, которые создают экосистему. Вспомните Балмеровское ”Developers!”

Разве у них не было компьютеров вроде наших ДВК?

Ведь в принципе ДВК был ничем не хуже оригинального IBM PC, а в ряде моментов (система команд, операционная система, вообще системное ПО) и лучше.

Разве у них не было компьютеров вроде наших ДВК?

Насколько я слышал, ДВК не были простой копией PDP-11, а были совместимой но более компактной и, соответственно, «дешёвой» системой. Вместо процессора на рассыпухе, в ДВК использовался микропроцессор. То есть, PDP-11 — это мини-компьютер, а ДВК — раб. станция или микрокомпьютер.

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

ДВК был ничем не хуже оригинального IBM PC

IBM PC был «дешевле», там не было защиты памяти, например. Сравните 2 корпуса ДВК и 1 корпус IBM PC. У СССР был ответ на это — семейство БК.

Но ДВК/БК, разумеется, сгинул вместе с СССР. :-(

MicroVax 2, 1985, 32 bit, single chip cpu, fpu, быстродействие сравнимо с VAX 11/750, до 16MB RAM, VMS, 4.3 BSD, насколько знаю первый микро процессор с виртуальной памятью, делали в разных корпусах, в том числе и такой

Отлично, теперь сравните характеристики с IBM PC, и вы увидите, что несмотря на приставку Micro это миникомпьютер по характеристикам. Ну и по цене, пишут, этот стоил 35к USD, а IBM PC — 1.5к USD.

Вот об этом я и пишу — DEC не стала лезть в персоналки, это её и сгубило.

с учетом того, что DEC в конце 80х поставляла сетевого оборудования примерно 10x Cisco, технология индексирования сети это altavista, которая могла стать больше чем google, не говоря уже про alpha и пр.,

с кем только не приходилось обсуждать эту тему из бывших коллег в том числе, не так это просто, но доля истины есть в том что Вы пишете, хотя уровня PC тоже делали позже

altavista, которая могла стать больше чем google

Говорят, для этого нужны были контакты с NSA.

alpha очень дорогая штука по сравнению с Intel. А в ИТ решения тяготеют к монополии.

про контакты это фантазии, про Intel доля истины есть, благодаря Intel risc desktop многие годы был недоступен, посмотрим что дальше будет, рано или поздно архитектура 8086/x86 уйдет

Мы можем уйти раньше. Про Интел я писал, глядя на то, как уходил SPARC. Если вы попытаетесь написать на С/С++/FORTRAN для SPARC из-под Intel, а потом из-под SUN, вы увидите, что Балмер был прав. Просто так, само собой, получается что-то, что работает хорошо на Intel и падает в непонятных местах на SPARC.

Поэтому кто продолбал десктопы, тот продолбал экосистему и монопольное положение в ней.
______________________________________

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

встречал ваше утверждение

возможно не совсем правильно меня поняли, конечно все сложнее, это примерно как про СССР сказать типа "курицу несущую золотые яйца резать бы не стали в 1991",

вероятно про СССР Вы знаете все или почти все, так почему он развалился?

возможно не совсем правильно меня поняли

Видимо.

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

Тяготеть к монополии решения в IT начали только в 1990-е годы. И это само по себе представляет интересный феномен. Случайно ли тут совпадение с формированием однополярного мира? Или это части одного события внезапного упрощения мышления? Тогда ведь на серьёзных щщах рассуждали о конце истории.

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

Капитализм, как известно, не умеет работать с монополиями. Это не социализм, который в любом существующему на данный момент варианте отлично работает с монополиями (например в стиле «будете выпендриваться — посадим верхушку»).

Просто до 90х была Тень СССР, которая сильно влияла на Запад в положительном смысле. А после 91 года она полностью исчезла. И даже сейчас Китайцы пока ничего не делают в софте.

Поэтому ответ: масштабное программирование — это технология следующего экономического строя, в нынешнем она работает плохо.

Это не социализм, который в любом существующему на данный момент варианте отлично работает с монополиями (например в стиле «будете выпендриваться — посадим верхушку»).

Кто именно эти самые «мы», которые «посадим», в случае, когда соцгосударство и есть корпорация-монополист в худшем смысле этого слова?

Кто именно эти самые «мы», которые «посадим», в случае, когда соцгосударство и есть корпорация-монополист в худшем смысле этого слова?

Иосиф Виссарионович легко посадит авиаконструктора, даже если в стране всего лишь 3 авиа КБ. И легко посадит производителя Windows, будет тот начнёт портить API.

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

И Зубину с Гамари легко намекнёт, что не надо ломать bootstrapp промышленного компилятора.

Иосиф Виссарионович легко посадит авиаконструктора, даже если в стране всего лишь 3 авиа КБ.

А кто посадит Иосифа Виссарионовича, когда тот начнёт зарываться?

Кто проверит, что его посадки действительно делают лучше, а не хуже? А то там, знаешь, потом было всякое, пересмотры, реабилитации…

И Зубину с Гамари легко намекнёт, что не надо ломать bootstrapp промышленного компилятора.

Ты в этом проблему видишь, я — нет. Что с этим делать будем? Сажать, если проигнорируют намёки, или нет?

А кто посадит Иосифа Виссарионовича, когда тот начнёт зарываться?

Группа товарищей вокруг, если увидит, что дела идут плохо.

Кто проверит, что его посадки действительно делают лучше, а не хуже?

Реальность.

Что с этим делать будем?

Avoid success at all costs.

Очевидно, сидеть на нелюбимых тобой С++ и Java.

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

В 1967 году выбор был непростой, но сейчас-то в ретроспектвиве ясно, что он оказался сделан единственно верным образом.

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

И, отмечу, что все эти ЕС тоже пошли стройными рядами на йух после развала СССР. Да и IBM их тянет постольку поскольку.

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

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

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

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

Ну и иронично тут то, что попинав ненавистные паттерны, совет "переходите на ФП" никак не отвечает на вопрос, как организовать свой код для решения поставленной задачи. От перехода на ФП архитектура магически не появится, а в конечном итоге для решения одинаковой задачи на любом ЯП нужно хранить и обрабатывать одинаковый набор данных. Потому что оный зависит от задачи, а не от ЯП. Внезапно, это будут те же объекты, управляющие структуры и паттерны.


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

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

В Smalltalk никакого вызова метода через точку нет.

Да собственно и самих методов :)

А кто ж тогда живёт в methodDictionary ? ))

О боже, они на свет лезут! Воинствующий дилетантизм in vivo!

Прежде чем выкладывать такие статьи рекомендую обращаться к рецензентам более-менее разбирающихся в теме - они уберегут и вас и нас от траты времени.

ЗЫ. Учиться, учиться и еще раз учиться (с) В.И. Ленин

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

Универсальной истины не существует. Уменьшение сложности в одном месте превращается в ее увеличение в другом.

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

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

Одни считают, что ООП – это инкапсуляция, наследование и полиморфизм. Другие приводят в пример объектные языки без инкапсуляции, без наследования, и пишут, что в функциональных языках полиморфизм более развит.

Некоторые пишут, что объектно-ориентировано должно быть проектирование, а не написание кода. Другие утверждают, что объектно-ориентировано и так вообще всё в мире.

Ничего не забыл?

Надо набросать концепт лиспоподобного субъектно-ориентированного языка.

Отличительная особенность: в AST всегда туплы размерности 2, потому что «я» всегда и так передается имплицитно. Результатом выполнения всех выражений — тоже всегда является субъект «я», что нечеловечески упрощает композицию. Пайпы становятся не нужны, потому что в данном контексте всегда один субъект «я». И так далее.

getAnimalName
getMammalName
getDogName
getCatName

Функции - это так круто!

Типичный ФПшный полиморфизм (видел подобное неоднократно)

function open(obj: File | Window | Conversation | Door | Lock | Chest) { 
  switch(obj.kind) {
      case "FILE": ...
      break;
      case "WINDOW": ...
      break;
      ...
  }
} 

Вроде книжка по рефакторингу рекомендует заменять такой код на наследование + полиморфизм)

В ФП нет наследования.

А ООП есть ;)

Даже у CLOS? Он вроде функциональный, как Lisp, но с элементами ООП.

Википедия так вообще говорит, что у него есть множественное наследование:

https://ru.wikipedia.org/wiki/CLOS

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

Под капотом у всего машинный код.

Ниправда!!

Иногда под капотом логика (а сверху верилог)!

(шучу, да).

Да, Javascript – это Lisp в овечьей С-шной шкуре ;)

Полиморфизм наследования - полное говно, статью почитайте еще раз.

Спасибо, у меня книжка ещё не дочитанная.

Классическое ООП — это конечно плохо, хотя оно и дало много хороших идей, которые ныне применяются даже и в не-ООП языках.

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

Если еще не читали, то рекомендую почитать SICP

Не существует ООП языков программирования. На любом языке можно писать код, как хочешь. И перестаньте оскорблять Java, C# и С++ программистов. Это как раз те, кто действительно может считаться крутыми инженерами! А вот какие-то Kotlin и Rust программисты - это уже ООП программисты, почти 100%. Их оскорбляйте :-D

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

Автору могу посоветоват смотреть старые видео Егора Бугаенко и почитать побольше статей с разбором SOLID.

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

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

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

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

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

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

Как-то однажды знаменитый учитель Кх Ан вышел на прогулку с учеником Антоном. Надеясь разговорить учителя, Антон спросил: "Учитель, слыхал я, что объекты - очень хорошая штука - правда ли это?" Кх Ан посмотрел на ученика с жалостью в глазах и ответил: "Глупый ученик! Объекты - всего лишь замыкания для бедных."


Пристыженный Антон простился с учителем и вернулся в свою комнату, горя желанием как можно скорее изучить замыкания. Он внимательно прочитал все статьи из серии "Lambda: The Ultimate", и родственные им статьи, и написал небольшой интерпретатор Scheme с объектно-ориентированной системой, основанной на замыканиях. Он многому научился, и с нетерпением ждал случая сообщить учителю о своих успехах.


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

Я то конечно согласен с утверждением, что ООП не единственная и не самая лучшая парадигма на свете, и далеко не всегда её применяют уместно. Люди начитываются клинкодов и книжек по паттернам и начинают писать по догмам, это всё понятно.

Не совсем только понятен момент по ФП. Вы специально ввели свою терминологию, где описали ФП как структуры + функции. Замечательно, то есть сюда помимо канонично функциональных языков программирования (Haskell, F#, Scala и т.д.) подходят как минимум C, Odin, Zig, наверное ещё Rust, а также в зависимости от стиля JS/TS, Python, C++, Go и многие другие языки.

Вы приводите пример с row polymorphism:

const getDisplayName = (user: {firstName: string, lastName?: string, middleName?: string} | null | undefined) => { ... }

Если что данной фичи нет в большинстве языков, которые я привёл. Так умеет только TS, а также JS и Python (из-за типизации). Почему это свойство приписывается к ФП (в вашем понимании), не совсем понятно.

Затем вы приводите пример с union:

type Shape = Circle | Rectangle

Тут лучше, хотя даже здесь я могу выделить C, C++ и Go, которые на уровне языка tagged unions не поддерживают. И всё ещё непонятно, почему данный способ приводиться как универсальный и заведомо лучший. Zig и Odin оба поддерживают tagged unions на уровне языка, но достаточно открыть реализацию структуры Allocator стандартной библиотеки для каждого из них и вот что мы увидим:

// Odin
procedure: Allocator_Proc,
data:      rawptr,

// Zig
ptr: *anyopaque,
vtable: *const VTable,

Ого, это ООП или ещё нет? По-моему вполне себе попытка эмулировать ООП, причём очень успешная, потому что люди заходят создавать свои аллокаторы, а не юзать юнион из 5 которые предоставляет std/core.

Потом вы называете методы мусором:

// Rust
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}
// Zig
const Door = struct {
    is_open: bool,
    pub fn open(door: *Door) void {
        door.is_open = true;
    }
    pub fn open_can_be_null(door: ?*Door) void {
        door.?.is_open = true;
    }
    // Door.open(door)
    // door.open_can_be_null()
};

А это можно называть методами? Думаю да. Языки ООП в привычном понимании? Вообщем-то нет, я бы оба отнёс к процедурным.

В целом непонятна эта неприязнь к методам, ведь как я уже сказал, если вы в условном C мы напишем глобальную функцию принимающую конкретную структуру, то никакого магического ducktyping по полям там не случится и переиспользовать эту логику для чего-то ещё не выйдет. Метод это обычная глобальная функция, где как вы сами сказали, есть неявный (а иногда и явный) this/self, и удобный вызов через точку. Тут нет никаких кардинальных отличий, и в некоторых языках это отлично видно. Тоже самое можно сказать про class/struct/type, зачастую это просто синтаксис, и разница видна только в конкретных языках в конкретных кейсах.

При всём этом вы приводите Go как язык, который вот уже почти подобрался к званию идеального, ведь там нет классов, но создатели таки оставили методы. Странно, потому что Go вроде как считается классическим ООП языком, там тебе и методы, и интерфейсы, и облегчённое наследование через включение полей структур, и инкапсуляция через camelCase.

Это уже не говоря о попытке показать инкапсуляцию для "ФП", когда мы используем замыкания, которых нет в части языков, которые я привёл, а в некоторых нет даже лямбд, и не во всех из них можно свободно и удобно кастить указатели на функции. Зато в Rust, Zig, а также частично в Odin, откуда-то взялись "pub" или "(private)". Скрыть поля или логику можно даже в C через opaque/incomplete types или static.

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

Статья выглядит больше как реклама TypeScript на фоне Java/C#, либо даже как реклама классического ФП (не в вашем понимании), но никак не как успешная попытка столкнуть между собой две парадигмы. Запинать ООП или тот же C# можно было бы гораздо изящнее.

Если вы внимательно почитаете еще раз, то речь там идет о методах классов. И в них нет ничего хорошего.

То что есть в Го и то что вы привели на Расте - по сути метод интерфейса, и по моей терминологии и близко не является ООП. И плохого в этом только то, что 1) появляется два способа вызова функций, через точку и без 2) в популярных редакторах первый - с автокомплитом, второй без.

А вот Zig как раз по сути ООП-шный (мусор), если там есть методы, и методы конкретного типа а не интерфейса (полиморфизм подклассов).

Про термин с ФП и ПП - я в примерах использую динамическое создание функций, с замыканием - это по вашему ПП?

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

Zig подход в данном случаи и правду выглядит как типичные ООП интерфейсы. Но это связано не с тем что это хороший подход а с тем что llvm, сделанный в первую очередь для плюсов, оптимизирует виртуальные таблицы лучше чем то что планировалось изначально https://github.com/ziglang/zig/issues/10052.

почему ООП — худшее, что было придумано в программировании

Дальше читать не стал.

UPD Автору не помешало бы поработать на проекте с > 1000 абстракций и десятком предметных областей.

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

максимально понятный человеческому мозгу набор класс — объект — свойства — методы

Э-э-э-э-э… Какие методы у объекта «моя кружка»? Класс — понятно: посуда (в ФП это будет тип). Свойства — тоже без сюрпризов: «красная, с ручкой» (в ФП — это статические данные).

А вот методы… Какие методы есть у моей кружки? «Сходи налейся чаем?» — не работает.

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

Найдите мне кейс, когда кружка может что-то инициировать.
Кружка - это объект-значение в терминах DDD (object value), и вряд ли будет иметь какие либо методы, только свойства и будет скорее всего использоваться в качестве свойства типа "кол-во кружек". ваш пример некорректен. Проще - кружка вряд-ли будет иметь id)

Я писал на erlang/otp несколько лет, и скажу, что лично мне трудно себе представить какой-либо большой продукт с кучей абстракций, инвариантов и тд.

А вы уверены, что куча абстракций - это вообще хорошо?

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

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

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

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

Это зависит от масштабов бизнеса.

ООП склоняет к введению огромного количества чисто операционных абстракций. Всякие там фабрики, интерфейсы, представления и тому подобное.

ну это то, что в ФП делается руками)

UPD
а в ООП это уже состоявшиеся паттерны, по которым есть инфа и много лит-ры. Еще раз напомню - я про коммерческую разработку, это важно.

Это зависит от масштабов бизнеса.

Ну я, скажем, как-то работал в большой внешнеторговой фирме, где было человек 200 бухгалтеров, но абстракции "бухгалтерия" в программах не было (конкретно я писал работу с определённым банком). Были: счета, платёжные поручения, валюты, курсы, плательщики. То есть вещи, имеющие конкретное выражение во входных и выходных структурах данных. А зачем программе абстракция "бухгалтерия"?

Очевидно же. Потому что у вас была одна бухгалтерия и она вообще не нужна, как сущность в системе и даже как объект-значение. Если бы их было бы несколько, то вам пришлось бы её задекларировать и давать каждой id, класть в БД например, и связывать счета с определённой бухгалтерией.

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

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

В случае с одной бухгалтерией - очевидно так.

Я никогда не видел предприятия с двумя или более бухгалтериями (не путать с двойной бухгалтерией).

Есть очень большие компании)

Если есть подразделения, ведущие самостоятельную хозяйственную деятельность, то они и учитываются отдельно.

Но я, впрочем, не буду настаивать, не моя тема, да и несущественно.

Поэтому тезис о «максимально понятном человеческому мозгу наборе» — туфта

Тогда как вы объясните такое явление, как анимизм?

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

Это следствие склонности людей к одушевлению неживых предметов.

А касательно ООП, там ведь методы это не действия самих объектов, а, скорее, действия с атрибутами объекта.

там ведь методы это не действия самих объектов, а, скорее, действия с атрибутами объекта

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

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

налить(что, куда)

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

Во-первых, я же не в пустыне заголосил; я на комментарий отвечал. Вот такой:

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

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

А сумму заказа тоже в отдельной функции считать?

«методы» — все равно функции, вызываемые извне (заказ сам себя не оплатит)

Глобальные функции? Вы же не будете все функции в глобальной области определять. Наверняка будете группировать их по модулям. И функции для работы с заказами логично было бы поместить в модуль (namespace) "Order".

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

И тут в чат врывается OCaml ;)

И тут в чат врывается OCaml ;)

Так я и не утверждал, что это одинаковые вещи. Наверняка, они ещё и различны в разных языках. Я просто плавно подвожу к обоснованности ООП. Основные проблемы ведь всё-равно одинаковые. И в разных языках они решаются по-разному.

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

функции для работы с заказами логично было бы поместить в модуль (namespace) Order.

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

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

высокое зацепление и проблемы с модификацией

Полиморфизм решает именно эти проблемы, а не создает их.

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

И в ООП полиморфизм ведь не запрещено использовать.

Handle pattern, Tagless Final, Free Monads, Effect's Systems – выбирайте что душе угодно!

Чего? Полиморфизм — это разная логика для разных типов.

в ООП полиморфизм ведь не запрещено использовать

Мы заходим на третий круг, я два комментария выше вам сказал:

это [использование методов] отрезает путь к полиморфизму без наследования (или приводит к дублированию кода в языках с duck typing)

Такое ощущение, что вы не понимаете, что такое ФП, и никогда с ним не работали плотно и долго. Я работал по нескольку лет с плюсами, джавой, хаскелем, эрлангом, эликсиром, руби и даже коболом, похапе и джаваскриптом. Поэтому мне, наверное, многие вещи очевидны настолько, что у меня не получается их объяснить на пальцах. Тут только опыт, скорее всего, подскажет.

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

Вопрос к тому, сколько кода придётся менять при изменении логики полиморфной функции? Не придётся ли менять все вызовы такой функции? Или ветвления добавлять? Или есть другие способы?

Что такое ФП я понимаю, но плотно не работал (начинаю изучать эликсир).

Не везде ведь нужен полиморфизм. В бизнес-логике, думаю, он вряд ли нужен.

Эм… Ну вот есть Order, есть Invoice, есть еще куча документов — и для них для всех актуальны агрегации по деньгам. Можно, конечно, определить интерфейс и заставить его имплементировать во всех классах, но это потребует внесения изменений во все эти классы.

Конкретно в эликсире, полиморфизм чаще всего ассоциируют с протоколами, хотя behaviours — тоже примерно про него.

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

defimpl Jason.Encoder, for: MyStruct do
  def encode(struct, opts) do
    struct
    |> Map.from_struct()
    |> Map.take(~w|foo bar baz|a)
    |> Jason.Encode.map(opts)
  end
end

Более того, иммутабельно на фундаментальном уровне!

Потому что новый заказ и оплаченный заказ — это две абсолютно разные сущности

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

Сколько видов сущностей надо завести под все возможные сочетания состояний заказа?

Одну: структуру данных. Я там выше это раз триста написал. Цитата, которую вы привели, относится к обоснованию иммутабельности структур данных. Order1 = pay(Order)

Как установить между ними всеми связи?

Обычно это делают с помощью конечных автоматов.

Может я чего-то и упустил, но тогда Вы говорите о состоянии заказа а не о самом заказе. ООП как раз исходит из сокрытия состояния.

Order1 = pay(Order)

и в функции pay() мы скопируем всё, начиная с австро-прусской войны, включая состав заказа, плательщика-получателя, менеджеров, которые его ведут и прочие погоды на Марсе, важные для прикладной задачи.

Обычно это делают с помощью конечных автоматов.

fsm и ООП никоим образом не противоречат друг другу, а наоборот, очень гармонично взаимно дополняют. А что с хранением истории заказа?

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

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

что с хранением истории заказа?

Да что угодно, я предпочитаю Event Source.

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

Реализовывал и реализовываю. Подвоха не заметил. Поясните плз, может я чёго-то упускаю?

Код должен гарантировать, что мы может оказаться в состоянии foo тогда и только тогда, когда предыдущее состояние — bar и мы получили event baz.

Только тогда можно говорить о математических гарантиях консистентности, предоставляемых FSM. Иначе это просто поле status из базы, которое может поменять как угодно кто угодно.

Иначе это просто поле status из базы, которое может поменять как угодно кто угодно.

Хорошо, пусть будет status (правда не пойму причём здесь база). И как кто угодно собирается поменять его значение, если оно private? Инкапсуляцию никто не отменял

База ни при чем совершенно, это я так, для красного словца.

Ну значит, вам придется защищаться от рефлексии, от аспектов, а еще нарисовать не поддающегося никакому когнитивному анализу макаронного монстра в setStatus.

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

Ну значит, вам придется защищаться от рефлексии, от аспектов, а еще нарисовать не поддающегося никакому когнитивному анализу макаронного монстра в setStatus.

Ничего из перечисленного мне не встречалось. А вместо setStatus(Status newStatus_) вполне уместны условные setError(), setIdle() и подобные. (Это если совсем примитивизировать - а на практике ещё и наборы для enter, exit определённые у классов иерархии state и transit у самой fsm)
Уж чего чего, а Его Макаронность, ООП как раз отлично структурирует в отличие от

ООП как раз отлично структурирует в отличие от

Вы ошибаетесь, что, скорее всего, вызвано недостатком опыта в одной из парадигм.

setError(), setIdle() и подобные

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

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

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

Вы в любой стейт позволяете попадать из любого другого? — Тогда это не FSM.

Если все-таки нет — то придется добавить проверку. Tertium non datur.

Мой опыт подсказывает, что если вы думаете, то вам нужен конечный автомат, то скорее всего вам не нужен конечный автомат. Безотносительно парадигмы.

У нас вся жизнь – один большой конечный автомат и матрица как инструмент задания переходов состояний! ;)

А мой показывает ровно обратное. Иначе три четверти кода займет проверка целостности данных.

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

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

Видео я не смотрю в принципе, пардон.

Ещё один "не читал, но осуждаю". Веб тут ни при чём совершенно.

Осуждаю? — я вообще ничего не осуждаю, даже вашу манеру вести дискуссию.

Напишите тектом — я ознакомлюсь. Видео, повторяю, не смотрю — никакие.

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

Мой опыт подсказывает, что если вы думаете, то вам нужен конечный автомат, то скорее всего вам не нужен конечный автомат. 

Мой опыт подсказывает обратное - везде где встречается сколь угодно отличная от нуля сложность - конечный автомат или их композиция делает систему прогнозируемой и отлаживаемой

data State
  = Building
  | Finalized
  | Paid
  | Shipped

BillingAddressType :: State → Type
BillingAddressType Paid = BillingAddress
BillingAddressType Shipped = BillingAddress
BillingAddress _ = ()

data Order state = Order
  { shippingAddress :: ShippingAddress
  , goods :: [Item]
  , billingAddress :: BillingAddressType state
  }

createOrder :: ShippingAddress → Order Building

addItems :: [Item] → Order Building → Order Building

finalizeOrder :: Order Building → Order Finalized

payOrder :: PaymentInfo → Order Finalized → Either PaymentError (Order Paid)

shipOrder :: Order Paid → Order Shipped

Можно набросать соответствующую стейтмашину на чистом ООП?

(это было в ответ на

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

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

Красота.

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

Можно набросать соответствующую стейтмашину на чистом ООП?

Я не знаю haskell и поэтому не приведу Вам аналогичный пример. Но да, очевидно можно, если приведённый код делает что-либо осмысленное

Пишем что-то про заказы.

У заказов есть состояния: составляется, закончил составляться (финализирован), оплачен, отправлен. Во всех состояниях у заказа есть адрес отправки и назначенные товары. В состояниях «оплачен» и «отправлен» у заказов ещё есть адрес выставления счёта, во всех прочих состояниях он просто бессмысленен.

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

Но да, очевидно можно, если приведённый код делает что-либо осмысленное

Я написал только типы, потому что всё самое интересное в типах. Реализации функций тут очень скучные, вроде

createOrder addr = Order addr [] ()

addItems items Order{..} = Order{.., goods = goods <> items }

finalizeOrder Order{..} = Order{..}

Так что, можно теперь ООП-вариант?

Да можно, конечно. Вы и сами это прекрасно понимаете. Но он будет уродлив именно из-за проверок доступности перехода. Но можно.

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

Человек прекрасно использует fsm с ООП, не беспокойтесь. Просто представьте, что поведение fsm определяется методами стейтов, которые за счёт полиморфизма наследования меняют свою реакцию на евенты. Никакого прямого изменения стейтов нет. Состояние скрыто. Конструкция банальна.

Я правильно понимаю, что каждый стейт — это инстанс класса, унаследованного от AbstractState (или имплементирующего соответствующий интерфейс)? И ивент делегируется текущему стейту?

Тогда единственный осмысленный принцип из SOLID, а именно — SRP — идет в лес. Потому что для добавления нового транзишена надо будет поменять 2 разных стейта.

Тяжело представить. Можете всё-таки показать пример? Я вот потрудился и набросал немного наиболее характерного кода.

Без проверок на цепочке Sealed классов можно сделать.
Будет чуть мене уродливо чем с проверками

Никаких других переходов в системе нет.

И выложив это дело в прод, вдруг, получаем следующие сценарии:

  1. Начинаем собирать товары, и выясняем, что 1 товара из 10 складе нет. Ну ничего, клиент готов взять чуть более дорогой аналог. Поэтому мы меняем этот товар.. а, нет, сделать его нельзя. Ну ладно, отменяем весь заказ.. а, нет, отмена не предусмотрена. Просим клиента собрать новый заказ, а этот так и остаётся маячить в системе.

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

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

И так далее. Какими словами клиент и владелец вспоминают зашкодивших это программистов додумайте сами.

Вы путаете конкретную задачу конкретного магазина и модельную задачу выражения стейтмашины. Вас просят решить задачу «Вася купил пять яблок за десять рублей, сколько стоит яблоко?», а вы начинаете рассказывать, что яблоки бывают разные, и вообще владелец магазина братан Васи и дал ему одно яблоко бесплатно.

А вот пример из жизни: программист в новом методе забыл проверить, что для размещения заказа на обмен валюты надо бы проверить депозит. И мы разместили заказ на обмен €75М.

Клиент передумал, депозита не было, штраф за отмененный заказ — 0.3%. -€250K с баланса конторы.

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

Никакая стейт машина не спасёт вас от криворукого погромиста. Откройте для себя контрактное программирование.

Вам апломб танцевать не мешает?

Вы мне еще посоветуйте про goto почитать.

Возможно тут откроете для себя что-то новое и про goto.

Можно набросать соответствующую стейтмашину на чистом ООП?

createOrder(ShippingAddress shippingAddress): Order {
  order = new Order();
  ...
  order.state = State::Building;
  return order;
}

finalizeOrder(Order order): Order {
  if (!this.isFinalizeAllowed(order)) {
    throw new Exception('...');
  }
  
  ...
  order.state = State::Finalized;
  return order;
}

Можно сделать классы OrderBuilding extends Order, OrderFinalized extends Order, но обычно так никто не делает.

Да, значение поля в типах не проверяется. А надо? А зачем? В коде явно указано, что finalizeOrder поставит только статус Finalized и никакой другой.

Да, другой метод может поставить Finalized без вызова isFinalizeAllowed. И с типами точно так же, берем и делаем метод setFinalized :: Order Shipped → Order Finalized. В чем разница?

if (!this.isFinalizeAllowed(order))

Рантайм-проверка же. Это совсем не то же самое.

Можно сделать классы OrderBuilding extends Order, OrderFinalized extends Order, но обычно так никто не делает.

Как тогда ограничить количество наследников-«состояний»?

И, кстати, из любопытства, как будет выглядеть создание OrderFinalized из OrderBuilding в вашем любимом языке? У них одинаковые поля, и в хаскеле я с RecordWildcards могу сделать finalize Order{..} = Order{..}, компилятор мне сам все поля перепакует (и при компиляции вообще превратит эту функцию в noop, потому что увидит, что не меняется ничего, кроме типов).

Да, значение поля в типах не проверяется. А надо? А зачем?

Чтобы компилятор проверил отсутствие заведомо бессмысленных операций или переходов.

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

В коде явно указано, что finalizeOrder поставит только статус Finalized и никакой другой.

Для того, чтобы сделать такой вывод из ООП-кода, мне надо читать весь код, включая детали реализации вроде «а какая тема письма, отправляемого пользователю, с уведомлением об отправке заказа». Чтобы сделать такой вывод в типизированном ФП, вы просто читаете типы функций, всё остальное проверяет компилятор.

Это примерно как если бы JS-разработчик из середины нулевых спросил «а зачем вам в функции объявлять, что она принимает только инт, и количество аргументов указывать? видно же из кода».

И с типами точно так же, берем и делаем метод setFinalized :: Order Shipped → Order Finalized

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

В ООП для этого придумали sealed классы. Но оно все равно уродливо. При таком подходе у нас получаются те же иммутабельные состояния, а там проще уж норм инструменты взять ;).
Еще там изобрели дата-классы для ограниченного набора дерайвинга. Экстеншины и миксины вместо тайпклассов. А все из-за конценпции наследования и ее недостатков.

Sealed классы — в смысле, для стейтов? На каждый стейт по классу?

На каждый стейт по классу?

Именно так. Мы же хотим переопределять поведение в зависимости от стейта. Фактически заводим объекту FSM вторую VTable

И когда нам захотелось перед done воткнуть стейт saving, придется поменять все классы, которые умели переходить в done, так?

Это как-то немного чересчур, нет?

И когда нам захотелось перед done воткнуть стейт saving, придется поменять все классы, которые умели переходить в done, так?

Когда нам захотелось добавить новый стейт, мы:

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

  • дополняем fsm методом gotoSaving()

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

Всё.
З.Ы. Мне кажется, что наша беседа имеет некоторый привкус вкусовщины. Безусловно, на любом языке можно реализовать что угодно, и на вкус и цвет все фломастеры ...
Предлагаю сойтись на тезисе, что FSM решает множество проблем и их применение в подавляющем большинстве случаев оправдано. Отдельно хочу отметить необходимость следования парадигме языка, который используется.

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

Подождите, я не понимаю. Как вы разделяете, что идёт в enter «следующего» состояния, а что — в exit «предыдущего»?

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

дополняем fsm методом gotoSaving()

Кто такой fsm? Это какой-то класс-менеджер? Инстанс конкретной стейтмашины, построенной по данным бизнес-правилам?

реализуем логику перехода в новый стейт, там, где это надо по задаче

«А теперь рисуем сову»

Кто это вызывает? Это вызывается снаружи fsm? Какова его роль тогда? В чём разница между gotoSaving и Saving::enter?

Подождите, я не понимаю. Как вы разделяете, что идёт в enter «следующего» состояния, а что — в exit «предыдущего»?

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

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

Вызываем у FSM соответствующий метод. FSM вызывает аналогичный у текущего стейта. Если логика предметной части (проверка реквизитов) прошла - стейт инициирует переход в Paid. если не прошла - в Error

Кто такой fsm? Это какой-то класс-менеджер? Инстанс конкретной стейтмашины, построенной по данным бизнес-правилам?

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

Как тогда ограничить количество наследников-«состояний»?

Никак, так же как у вас. Сколько сделали типов, столько и будет. Зачем их ограничивать?

И, кстати, из любопытства, как будет выглядеть создание OrderFinalized из OrderBuilding

new с копированием полей. Потому так никто и не делает.

Чтобы компилятор проверил отсутствие заведомо бессмысленных операций или переходов.

Так мы сами задаем осмысленные операции, а не компилятор. Они идут из бизнес-требований, компилятор про них не знает.

я сейчас могу сделать дальше с заказом

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

чтобы сделать такой вывод из ООП-кода, мне надо читать детали реализации

В том и дело, что finalizeOrder совсем необязательно должна ставить какой-то статус. Что надо делать, описано в бизнес требованиях. Там много всего, и получения нужной информации все равно надо смотреть реализацию. Может нас интересует не статус, а меняется ли поле updatedAt, или с каким заголовком отправляется письмо. Всё в типе не опишешь.

Это примерно как если бы JS-разработчик из середины нулевых спросил

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

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

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

Никак, так же как у вас. Сколько сделали типов, столько и будет. Зачем их ограничивать?

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

new с копированием полей. Потому так никто и не делает.

Тяжеловесный синтаксис мешает выражать мысли, не удивлён.

Так мы сами задаем осмысленные операции, а не компилятор.

Мы задаём (постулируем) атомарные операции. Осмысленность их композиции проверяет компилятор.

Да и даже осмысленность их задания компилятор проверить может — выявляемый косяк с размером фрейма композиции энкодеров я описывал рядом.

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

Новая функциональность может быть сделана в терминах «описать пайплайн для заказов со скидками и промо-акциями». И тут type-driven development вместе с type-driven-рефакторингом очень помогают.

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

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

Всё в типе не опишешь.

Опишешь :]

Но даже если бы это было не так,

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

— стремление снизить необходимость лезть в детали реализации ИМХО осмысленно и вполне естественно.

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

Типы переменных это техническая информация, а статусы заказа это бизнес-требования.

Типы переменных тоже выражают бизнес-требования. Телефон — скорее строка (а не число) потому, что «phone 0123» ≠ «phone 123». Количество компьютеров на складе — целое число потому, что у вас не может быть пол-компьютера. Деньги в трейдинговой системе выражаются как fixed point с N разрядами после точки потому, что таковы бизнес-требования.

Можно пытаться изложить бизнес-требования средствами для технической информации, но мы возвращаемся к вопросу "зачем?".

Для ответа придётся вернуться к тезису «чтобы компилятор проверял их выполнение.»

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

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

oldState = this.state;
this.state = Finalized;
this.doSomethingExpectingFinalizedState();
this.state = oldState;

просто потому, что так проще, я видел.

У вас — можно

Ну и пусть. Если программист пишет новый класс extends Order, значит ему так надо для его задачи. Не вижу причин это контролировать дополнительно компилятором. Тем более что это все равно редко используется.

потому что к нему применим строго определённый набор операций

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

Какие-то задачи с ними проще, какие-то сложнее. Описать 10 типов сложнее, чем 1. Отмена может быть возможна из 6 разных состояний, с типами придется их все указывать в аргументе и обновлять при добавлении нового разрешенного для отмены. А для одного из них есть дополнительный критерий отмены, который надо проверять логикой, и часть требований будет в типах, а часть в алгоритме. А другому программисту надо будет с этим всем разбираться. А так открыл isCancellationAllowed и проверил все критерии, добавил новый если нужно.

Опишешь :]
стремление снизить необходимость лезть в детали реализации

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

Типы переменных тоже выражают бизнес-требования

Они подбираются под бизнес-требования, но выражают формат данных, которые лежат в переменной.

Для ответа придётся вернуться к тезису «чтобы компилятор проверял их выполнение.»

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

Это вполне может быть скрыто где-то в деталях реализации в ООП-коде

Ну так и с ФП можно написать только 1 тип, а не как у вас.

Ну и пусть. Если программист пишет новый класс extends Order, значит ему так надо для его задачи.

Если программист делает ошибки, значит ему так надо для его задачи.

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

Чтобы компилятор видел, опять же.

Описать 10 типов сложнее, чем 1.

Какие 10 типов у меня указано выше? У меня выше ровно два типа: тип, описывающий возможные состояния, и тип заказа. На сдачу ещё можно функцию, вычисляющую тип billing address'а по состоянию, посчитать, хз, но это тоже O(1) от количества состояний.

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

С чего бы?

CanCancel :: State → Bool
CanCancel Finalized = True
CanCancel Building = True
CanCancel _ = False

cancel :: CanCancel st ~ True ⇒ Order st → Order Cancelled

Добавляете просто ещё один кейс в CanCancel.

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

Нет, зачем?

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

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

Это практически то, о чём пишут всякие дяди бобы, только сделанное по-нормальному и проверяемое компилятором.

Я все-таки имел в виду без этого. Вы не убираете необходимость лезть в детали, а вытаскиваете их наружу в виде типа, уменьшая уровень абстракции.

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

Они подбираются под бизнес-требования, но выражают формат данных, которые лежат в переменной.

Формат данных и его семантику. Целое число. Положительное число. Делитель вон того числа.

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

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

Ну так и с ФП можно написать только 1 тип, а не как у вас.

Ну да. И с ООП можно всё написать в одном классе, прямо в public static void Main.

Только в ООП тяжело требовать от компилятора проверок, а в типизированном ФП — легко.

Я, кстати, прислушался к вашим заклинаниям про «компилятор должен давать по рукам нерадивому мне» и в своей библиотеке, реализующей FSM (pushdown automata на самом деле), на слаботипизированном эликсире — проверяю всё, что можно, — на этапе компиляции и даю по рукам, если что.

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

Пример чего?

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

Sidenote: рантайм ошибку с бесконечным полем в тексте по ссылке Идрис бы решил одной левой. В Хаскель, вроде, какое-то подобие завтипов завезли, поэтому, наверное, можно и там, но мне лень ковыряться.

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

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

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

Я не сказал «являются», я сказал «построены на». Или вы утверждаете, что PDA не построены на конечных автоматах?

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

Заказ сохраняется, стейт — нет.

А стейт тоже надо сохранять, чтобы потом фильтровать по нему SQL-запросом в админке или в каком-нибудь массовом обработчике, который отправляет все оплаченные заказы в другую систему. Даже просто вывести, вот открыл менеджер админку, как ему определить, заказ 1234 доставлен или нет? Любые данные, по которым вы определяете стейт при создании объекта в приложении по данным из базы, можно поменять SQL-запросом.

стейт тоже надо сохранять […] в админке

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

Любые данные, по которым вы определяете стейт при создании объекта в приложении по данным из базы, можно поменять SQL-запросом.

SQL-запрос на изменение баланса счета клиента выполняется с другими привилегиями из другой подсистемы, очевидно. У разработчиков основного приложения read-only доступ.

Что вы мне в принципе пытаетесь доказать, интересно? Что я галлюцинирую, а никакой защиты — лучше, чем какая-то, пусть неполная (хотя в моем случае она близка к полной)? Да даже если забыть про права доступа и хранить поле в базе (которой во многих проектах просто нет, ну да ладно) — неужели вы не понимаете, что допустить ошибку перехода в запрещенное состояние — это нормальный рабочий сценарий, а целенаправленно поменять поле в базе на «заказ оплачен» — саботаж, и между этими двумя действиями — гигантская разница?

Админка — это что-то на джейсоноукладочном.

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

когда на каждый активный заказ запущен гринтред

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

Что вы мне в принципе пытаетесь доказать, интересно?

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

допустить ошибку перехода в запрещенное состояние — это нормальный рабочий сценарий

Вот я и говорю, если мы пишем новый код, например добавляем метод shipOrder, то никакой существующий код нам не помешает случайно написать shipOrder :: Order Paid → Order Finalized. Тут нет разницы между "меняем снаружи" и "меняем изнутри" чего бы то ни было, мы в любом случае пишем новый код там, где есть возможность менять состояние произвольным образом.

Вот я и говорю, если мы пишем новый код, например добавляем метод shipOrder, то никакой существующий код нам не помешает случайно написать shipOrder :: Order Paid → Order Finalized.

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

Ну как это не надо, если доставки у нас еще нет, и надо ее написать. Значит и в алгебре такого действия нет.

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

Работаю сейчас с системой екомерса, где ВСЯ бизнес-логика построена на event-subscriber модели, и ВСЕ переходы между состояниями заказов наклацываются из админки. То есть условно функция finalizeOrder просто бросает ивент и больше ничего не делает, а дальше обработчики уже решают, какой конкретный статус заказа будет присвоен.

Получается, что тут вообще нет нужды в типах заказов, потому что он как бы всегда один и тот же?

То есть условно функция finalizeOrder вообще может перевести заказ в состояние «новый». Удобно, чё.

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

Поэтому бизнес-аналитик немного вешается 🤷‍♂️

Одну: структуру данных.

Лучше с помощью ADT/GADT

Я не вижу существенных плюсов в ADT/GADT, разве что в зависимых типах, да и то. Дело вкуса, но мне лично не требуются шоры, чтобы не отвлекаться на пасущихся сбоку кобылок.

Для описания различных инвариантов одной сущности

Я не говорил, что я их не понимаю. Я сказал, что не вижу в этом существенных плюсов.

Тип сущности один, а вариантов конструкторов столько, сколько требует бизнес-логика. Можете провести аналогию с енамами на стероидах или силд-классами

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

hot_cup = programmer.pour_some_tea(Cup())

Но, повторюсь, объекты типа кружка - это как правило value object и не имеют поведения, только кол-во

в системе должна быть очевидно ещё одна абстракция

Кому должна? Почему очевидно? Чем плохо просто работать с голыми данными (кружка) и функциями?

Если мы говорим про состояния — а это уже FSM, со всеми вытекающими, — ООП будет очень сильно мешать.

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

И если вам важно, налита она или нет и не важно кто или что ее налило - то это уже ее свойство.

Мне нужен ответ? Помилуйте. Мне — не нужен, потому что я дискутирую с тезисом:

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

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

Все так. И все вот эти неодушевленные предметы - это обычные value object, обычно не имеющие поведения и прекрасно ложащиеся на концепт ООП+DDD ) а то, что у вас функции сбоку, так это добавляет сложности, так как у вас теперь методы "обезличены" (или всё-таки нет?) и во-первых вам нужно их куда то осмысленно класть в исходниках, во-вторых в этих функциях теперь вам нужно понимать, какой объект у вас на входе. И, не забывайте, в ООП тоже есть функции вообще-то. И никто не запрещал их использовать)

Сам концепт ООП нужен для инкапсулирования сложности. Вероятно, есть и другие методы, но ООП выглядит вполне удачным

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

На синтетических примерах с Animal/Dog/Cat — выглядит. А в реальной жизни в энтерпрайзе — код разбухает в геометрической прогрессии и начинается программирование абстракций ради абстракций, чтобы заткнуть дыры применимости. Вон даже FSM с гарантиями не построить, как мы видим по примерам выше.

Эти "люди" в лице полутора местных землекопов, очевидно, не умеют готовить ООП. Как правильно это делать я описывал тут. Там нет кучи абстракций и разбухания кода.

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

Впрочем, в «энтерпрайз» эти люди тоже не умеют […]

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

что приводит к комбинаторному взрыву

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

Как правильно это делать я описывал тут.

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

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

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

Ничего я не забыл, читайте дальше.

Ничего я не забыл, читайте дальше.

Я pulp fiction не очень, спасибо.

Ваш выбор. Вы только корону поправьте, а то глупо смотритесь, когда она вам глаза закрывает.

А мне нравится выглядеть глупо.

Эти "люди" в лице полутора местных землекопов, очевидно, не умеют готовить ООП.

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

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

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

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

Формально у вас любой код на любом языке, в любой парадигме FSM

Формально у вас любой код на любом языке, в любой парадигме FSM

Вы абсолютно правы. Любой объект, меняющий своё поведение в зависимости от своего внутреннего состояния есть нечто похожее на fsm. Лично я под FSM подразумеваю подход при котором осуществляется выделение состояния в отдельный регистр, сущность. Тем более кажется несколько чужеродным применение fsm в парадигмах не предполагающих наличие скрытого состояния. Всё ИМХО.

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

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

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

Чёт в реальном энтерпрайзе, если и есть некоторое количество систем на Haskell, то чисто следовое. Все мучаются с ООП.

Вон даже FSM с гарантиями не построить, как мы видим по примерам выше.

Какие гарантии должны быть у FSM? Отсутствие тупиковых/недостижимых стейтов? Не надо решать несуществующую проблему

Не ожидал именно от Вас услышать такой аргумент

Это не аргумент. Это ответ на ваше столь же бездоказательное заявление. В смысле «ваше слово против моего».

Все мучаются с ООП.

Миллионы мух не могут ошибаться. Да и не все.

Не надо решать несуществующую проблему

Не нужно объявлять проблемы других людей и бизнесов «несуществующими».

Это не аргумент. Это ответ на ваше столь же бездоказательное заявление.

Какое именно? А уж по поводу меренья опытом, то в вашем списке отсутствует Smalltalk, на котором у меня имеется достаточно обширный опыт написания коммерческих систем, и без упоминания которого, как впрочем и работ Алана Кея, разговор об ООП ... (Далее про устриц от Жванецкого)

Не нужно объявлять проблемы других людей и бизнесов «несуществующими».

Так какие такие гарантии должны быть у FSM?

Мне не платили денег за смолток, а врать я не приучен.

Упоминать смолток и Алана Кая в разговоре про современный «ООП», как он реализован в джаве, шарпе, или, простихосподи, тайпскрипте — кощунство. Мы тут обсуждаем то, про что Кай сказал: «Я ввел такое понятие, как объект, но я имел в виду абсолютно точно не это!» (цитата по памяти).

Так какие такие гарантии должны быть у FSM?

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

Мы тут обсуждаем то, про что Кай сказал: «Я ввел такое понятие, как объект, но я имел в виду абсолютно точно не это!» (цитата по памяти).

Алан Кей говорил вообще не это:
"Я придумал термин «объектно-ориентированный», и я уверяю вас, что не имел в виду C++"
Про объекты он говорил, что им уделяется слишком много внимания, главное - обмен сообщениями

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

Код должен гарантировать, что мы может оказаться в состоянии foo тогда и только тогда, когда предыдущее состояние — bar и мы получили event baz.

Ну так осуществляйте переход в foo только в качестве реакции на event baz в состоянии bar. В нормальной реализации это самоочевидно.

не смею больше отнимать время.

Взаимно

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

И вообще, у вас довольно странное абстрактное мышление) С чего вы вообще взяли, что метод наполнения кружки должен лежать в классе кружка? Если вам по бизнес ложике важен процесс наливания чая, то вообще этот процесс инициирует не кружка, а вторая абстракция, которая собственно это делает. Всё как в жизни, не так ли?

Не так.

В моей жизни абстракции не совершают действий. Да и сама приколоченная гвоздями возможность наделить объект — методами — в моей жизни встречается крайне редко.

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

будет гораздо сложнее с функциональным образом мышления

Citation needed.

Потому что я был по обе стороны баррикад, и вынес ровно обратные впечатления. Более того, я уже стопиццот комментариев вам демонстрирую, что мозгу так не проще. И никакие фабрики и представления не потребуются. И я ни ногой обратно в ООП парадигму.

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

Цитаты не нужны, нужна просто статистика по ЯП в ентерпрайзе, всё. Коллективный разум порешал все давно.

О, это мой любимый тезис: сто миллионов мух ошибиться не могут.

У гугла энтерпрайз? Что ж они голанг-то тогда втаскивают всеми силами?

А еще раньше весь энтерпрайз был на коболе, причем даже тогда, когда уже существовали ООП языки. С этим как быть?

IBM-мейнфреймы, сделанные для энтерпрайза, — как там с ООП?

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

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

У гугла энтерпрайз? Что ж они голанг-то тогда втаскивают всеми силами?

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

А еще раньше весь энтерпрайз был на коболе, причем даже тогда, когда уже существовали ООП языки. С этим как быть?

И на что этот энтерпрайз с кобола смигрировал?

для таких задач я выбираю rust и радуюсь

Ну успехов распараллелить раст на 100К [грин]тредов. Или в кластер из 50 машин. Мы же все еще про энтерпрайз?

чтобы джуниорам было просто код писать

Я чё-то запутался. Вы всю ветку доказывали, что «просто» — это когда ООП, не? Или ООП просто только для тех, кто собаку в разработке съел, причем от хвоста до головы — исключительно в ООП?

на что этот энтерпрайз с кобола смигрировал?

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

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

Ну успехов распараллелить раст на 100К [грин]тредов. Или в кластер из 50 машин. Мы же все еще про энтерпрайз?

Без проблем. Смотрите tokio и асинхронный подход.

профит от его использования на масштабных проектах — очевиден

Ну очевиден — значит очевиден. Я сдаюсь. Мне не очевиден, но, видимо, это со мной что-то не так.

tokio

Не нужно принимать собеседников за идиотов, пожалуйста. Я прекрасно осведомлен о существовании tokio. Внутрикластерное взаимодействие она не решает никак, а насчет 100K нитей — ну попробуйте просто.

не успеете оглянуться, как начнете реализовывать "интерфейсы, фабрики и представления"

Типы наше все!

Забавная статья

Гениально! Если развернуть все комментарии, то вкладка Хрома сжирает больше гигабайта оперативы, а процессор начинает гудеть! Великолепная статья!

Это ещё цветочки. Гляньте эту статью: https://mol.hyoo.ru/#!section=docs/=u4oxgj_q8dpwd

Иронично, что используемый на Хабре Vue использует бесклассовый подход, как предлагает автор этой статьи, а вот в куда более экономной по ресурсам реализации на $mol всё построено на классах.

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

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

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

Лексический контекст – это понятие времени компиляции. В рантайме здорового человека его нет.

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

Лексический контекст – это понятие времени компиляции. В рантайме здорового человека его нет.

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

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

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

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

Мне сложно судить о вещах, которых у меня нет конкретного опыта. Я для таких вещей использую Scheme, и никаких стековых фреймов (и вообще стека в обычном понимании) в ней нет. Зачем нужен стек в Javascript, я не знаю. Скорее всего, это какой-то рудимент. Или ради интроспекции контекста, как в питоне (хотя питон вроде не хранит объекты в стековых фреймах).

Я для таких вещей использую Scheme, и никаких стековых фреймов (и вообще стека в обычном понимании) в ней нет. Зачем нужен стек в Javascript, я не знаю. 

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

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

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

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

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

Таким образом: не возникает никаких проблем с хранением динамического контекста (так как динамический контекст – это обычная структура данных – продолжение, размещаемая в куче, как и всё остальное); замыкание не требует никаких ресурсов, так как это обычные ссылки на структуры данных в куче, а лексический контекст не требуется на этапе выполнения; автоматически оптимизируются концевые вызовы; невозможно переполнение стека; возможны параллельные вызовы без отведения памяти на дополнительные стеки.

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

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

Спасибо за развёрнутый ответ. У нас исключительно терменологические расхождения. Вызванный continuation - суть фрейм, а есть ли аппаратная поддержка стеков, и сколько их в реализовано в железе - штука сильно вариативная. Аллокация фрейма в хипе достаточно распространённый подход, но имеет и свои минусы - например снижение производительности при реализации stackless coroutine по сравнению с stackfull. Но без этого не реализовать замыкания. Как Вы правильно отметили, при рекурсивном вызове при невозможности оптимизации хвостовой рекурсии нет иного варианта, как плодить фреймы на каждый вызов.

Тонко подмечено.

Помнится, в дедушке в 1995 году всё работало мгновенно с одним мегабайтом оперативной памяти, при гораздо лучших возможностях редактирования.

Публикации