
Комментарии 1170
ваш код вообще неподдерживаем ... что вы будете делать, если
Я уже от вас много раз слышал "а что будет если", и без проблем реализовывал ваши требования. Как я говорил это был последний раз.
Зря я, конечно, упрощенный пример вам написал
За несколько попыток не смогли придумать ничего сложнореализуемого на ФП, и дальше не придумаете.
вы все-равно почти используете ООП
Если считать замыкания ООП то можно что угодно назвать ООП кроме чисто процедурного подхода. И даже его - ведь this с точкой по сути это подстановка первого аргумента.
Принципиальная разница - классы, которые:
являются результатом раннего связывания данных к конкретному типу. В моем примере функции существуют в несвязанном состоянии, и связываются с замыканием только во время выполнения. При желании их можно переиспользовать в любом другом типе в отличие от методов класса.
классы для переиспользования наследуются, получая все данные и методы другого класса. В моем случае я компоную, как вы говорите, объекты, из того что мне нужно, без наследования, и не получаю то, что мне не нужно.
Современное ООП и ФП довольно похожи, особенно если использовать замыкания а не писать в чистом процедурном стиле, и принципиальная разница именно в этом - никаких классов, наследования и прочих прелестей ООП языков, а компоновка типов как конструктор, при этом минимальное, позднее связывание данных с функциями, а не раннее и повсеместное. Сделать тип для летающей робособаки на колесах с мозгом обезьяны, которая на поглаживание крякает - вообще не проблема для такого подхода (пример как крайний случай). Тогда как типичный ООП-шник облажается уже на робособаке.
И конечно возможность писать в простых случаях вообще без связывания, в процедурном стиле, без необходимости в лишнем синтаксисе типа статических классов и тп.
Многие минусы современного ООП на классах можете еще раз перечитать в статье.
Все по делу.Просто напрашивается - а что тогда использовать? TypeScript это еще та штучка(слишком много обвязок и предварительных настроек) Явно нужен какой то новый язык Вопрос какой?(Хаскель не предлагать как язык широкого применения не проходит) Ваши варианты?
Аналогично. Хайлоад (АБС крупного банка). Основной инструмент - RPG (альтернатива COBOL на платформе IBM i - те же задачи, но возможностей побольше и синтаксис в современном диалекте человеческий). Чисто процедурный язык. Синтаксически простой, но мощный и быстрый.
Плюс возможность собирать программу из кусков на разных языках. Так что где надо (и удобно) - можно и на С (край - С++) написать какие-то функции.
Язык должна определять задача
и компетенции команды. 100 задач, пара смежных команд на 10 человек.
даже выбрать два языка под эти 100 задач - уже повод для дискуссий
А что не нравится в Julia?
Меня особенно впечатляет mmap
Рельсы думал это венец творения в руби, а оказывается погубили его
К экосистеме эрланга этот давно питает нежную любовь, но применить пока нигде не получается… )
А вы Erlang не упомянули.
В конце статьи я указал языки, которые я предпочитаю, и почему. На том же TypeScript прекрасно пишется и веб, и мобилки (сам я сейчас React Native разработчик), и серверный бэк, и десктопы.
Как вариант бэк на Go.
Для максимальной скорости - C.
Но согласен, что идеального пока нет. Нужно его сделать.
Да и там не потянет... Если вдруг понадобится обработка сотен миллионов записей. И побыстрее.
Нет, скорей всего вы пишите в VS Code или ваши коллеги, который прекрасно работает на JS быстрее чем продукты JvmBrains.
Я работал в нем не так давно на своем стареньком макбуке с 8Гб памяти, параллельно запуская симулятор или эмулятор мобилки.
По поводу бэка - на NodeJS одни из самых производительных приложений (конечно если не сравнивать с C/Rust и тп), используются в AWS, Netflix, Paypal, геймдеве и много где еще. И уж куда производительнее Java (писал в той же статье про многопоточку и очереди).
В вашей статье про многопоточку и очереди я не нашёл ни слова про System.Threading.Channels , что выдаёт уровень экспертизы.
Нет, скорей всего вы пишите в VS Code или ваши коллеги, который прекрасно работает на JS быстрее чем продукты JvmBrains.
Не сразу понял, что у вас JvmBrains это JetBrains (на опечатку не похоже). Я пользуюсь и тем и тем, и VSCode всё же уступает. К тому же там поддержка некоторых языков и фреймворков сильно хуже.
Автор 1 раз в статье и один раз (минимум) в комментах написал JvmBrains.
При этом статья в хабе Java, но про Джаву ни слова (C# & TS), кроме дописывания до кучи в списке других языков пару раз.
При этом какие то статьи по Джаве еще есть.
При этом сравнивает JetBrains и другие IDE для Джавы не сравнивая мега разницу в возможностях...
Выше пишет, что Нода "уж куда производительнее Java".
Складывается впечатление, что автор не сильно знает Джаву. Просто дописал до кучи.
П.С. По самой статье: все что из нее нужно вынести - не нужно быть адептами никаких сект и слепо верить догмам. Нужно выбирать из всех возможностей и парадигм что есть. Выбирать комфортную вам и вашей команде под задачу и ресурсы. И быть гибкими в своем выборе.
Автор явно старается и ищет упорно и активно свой идеал. И он молодец в этом поиске. Видно, что много читает и интересуется. В общем путь идет достойно. Тут вопросов нет.
Но в статье же призывает сменить одну секту на другую как он. Что мне кажется ошибочным. И со временем мне кажется, что он прийдет к тому, что нет идеала ни где, а есть куча мнений как решать задачи. И инструментов. И нужно уметь выбирать. И это он научится на своем пути.
Джет Брейнс лучший инструмент. Но он уступает в одном и самом важном. Лучшую поддержку использования возможностей современных LLM. А это критическое превосходство. Поэтому придется заплатить за Курсор, и просто переключаться между IDE когда понадобятся инфраструктурные правки
По мне, так современные LLM это не то, что должно использоваться в разработке. ИМХО, это должна быть более надёжная система, а нынешние системы ещё в зародышевым состоянии. Это выглядит как откручивание винта ножом.
Подтвержаю, вот в Пейпал, а заодно рендеринг Нетфликса и Шопифая держат всего-то по четыре пользователя.
Да там скорее всего как в Facebook - формально используется php, а фактически он транслируется в код на C++, который потом компилируется.
Нет, они даже статьи писали что после переписывания на TS с джавы работать все стало в 10 раз быстрее при меньшем количестве кода, и разработка быстрее при меньшем количестве разрабов.
Может, так как в фреймворках Джавы по умолчанию (особенно тогда) очень плохая многопоточка - блокирующие операции и синхронизации, отдельный поток на каждый запрос и тп.
Это означает, что код на Яве писали люди с недостаточной квалификацией, которые не знали характерные идиомы. Ява используется аж в алготрейдинге. Ява была разработана для Сановских серваков, которые лет 15 назад перешли на совершенно видеокартную многопоточность — под 256 потоков исполнения.
10 раз — это либо переход от наивной виртуальной машины без JIT к компилируемому языку (по опыту OCaml, где есть 2 кодогенератора), либо более оптимизированный исходный код.
С учётом того, что Java выполняется как раз с JIT, то остаётся только алгоритмическая оптимизация. То есть, если бы переписали с Java на Java, получили бы то же самое.
Посмотрел бы на этот алготрейдинг,
Ну понятно всё с ними, разумеется. Но тем не менее, если они до сих пор не разорились (UBS), значит как-то оно работает.
И по похожим причинам мой алготрейдинг по факту однопоточный.
Это две разные задачи.
Я к тому, что на простом перекладывании JSON'ов переходом с Java-на-что-то без конкретного переписывания алгоритмов, десятку производительности не получишь.
Это может означать только то, MVP на джаве писали три с половиной джуна по методу ad-hoc-ad-hoc-и-в-продакшн, а переписывали с учетом всех набитых шишек — нанятые на уже прибыльный продукт профи.
Вы сейчас описали практически всю J2EE, лол.
Да я читал про этот инструмент. Идея проксирования на более низкоуровневые языки широко используется всеми. И в целом даёт существенное превосходство.
Голословно конечно можно говорить что серверный TypeScript для калек, но что-то больше напоминает брюзжание и юношеский максимализм.
Как и десктопы, так то есть целые быстрые и шустрые редакторы кода с полным фаршем и не требующие особо железа.
Это как посмотреть на сайт который студент за два дня слепил из ответов со стаковерфлоу и после этого говорить что весь интернет целиком ущербный.
больше напоминает брюзжание и юношеский максимализм
Не просто напоминает, а по факту таковым и является
JS уже перестал для отрисовки UI на десктопах по сути подымать оверхед в виде инстанса браузера для отрисовки "веб-приложения", коим весь JS и является? Спасибо хоть часть фреймворков не таскает с собой целый монструозный хромиум, как это делает электрон.
Я писал для десктопа на JS до того как появился электрон, ещё в 2009. Но уверен что можно было и ранее. Просто там открывался текущий системный браузер. Впрочем, однажды я писал для встраиваемой Opera, у которой было вырезано всё лишнее. Уже чуть больше весило, но всё же. Под Windows XP нормально было. А сейчас есть платформы, которые также открывают системное. Да чего уж там - Telegram с mini-apps, там тоже браузер с некоторым набором пробрасываемых данных.
Электрон и NW конечно толстоваты, но там бизнес задача была с написанием так чтобы работало везде и можно было скачать на любую платформу и чтобы работало одинаково, а окружение зафиксировано в стиле Docker - внутри. Да, такое требует много памяти, но вроде нынче память сильно дешевле. И по факту чтобы сделать вот очень качественно - надо делать действительно качественно и тогда электрон работает вполне себе хорошо, больше ест, но не тормозит. А вот когда делают в стиле главное побыстрее и так сойдет, либо если в разработке корпоративный ад с бюрократией и квадратичными алгоритмами... тут всё что угодно тормозит, просто на электроне это заметно значительно сильнее.
Так я и не против, каждой задаче свой инструмент. И первый код я писал для дедушки нынешнего tauri - тот же подход, просто они переизобрели то что я ещё для XP писал.
Можно было карму и не минусовать 🙃
Нечто похожее, только изначально там конструктор UI для десктопа со своим языком и прочими плюшками, и встроенной IDE. И бонусом веб, куда можно было прокидывать биндинг на внешнюю функциональность. Ну и те же VBScript и JScript, так что HTA, но протюненый. Делали, как ни странно, испанцы, называлось NeoBook. Сейчас вроде оно как-то даже живо, но теперь там вроде ближе к электрону и вообще забвение. А вот 16 лет назад нормально было.
Ещё меня однажды звали писать UI для теликов, в 2013, тогда там обычно был браузер с кастомными html и расширенными привилегиями для JS.
Вообще мир веба значительно больше чем некоторым кажется. Хотя пик развития в 2014 примерно был, сейчас нового на порядки меньше, хоть и бывает.
производительность бэка это больше результат правильной архитектуры и правильного разделения read/write моделей и баз данных под них нежели результат выбора технологии.
Значит с высокой долей вероятности вы изобрели велосипед.
А что у вас есть?
Согласен, и я десяток знаю.
Как студенты? Или использовали в продуктиве на протяжении нескольких лет? Я вот активно пишу на С++ и питоне уже больше 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 который является интерфейсом.
Ограничений в ФП нет вообще, любой ООП код можно без проблем переписать на ФП, и статья говорит о том, что он будет только лучше (на хорошем языке разумеется).
Но вы так и не написали, ну точнее написали но непонятно как оно работает.
Хотите реальный челендж? Вот довольно простой легко читаемый код, в котором легко разобраться, легко добавить новый тул, легко использовать тул
Напишите ваше решение в вашем стиле без ООП
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 который является локальным только для этой тулы. Я думал это очевидно что состояния у каждой тулы своё и хотел увидеть как именно это вы решаете видимо вам явно надо это показать.
Ну и ваш код сразу же проспится если в тулы добавить больше методов, особенно если учитывать, например что часть методов нужно переопределять а часть нет. То бишь явно проблема скалирования.
Я думал
Думал но не сказал, молодец. Код выводит то же самое, если нужно состояние очевидно нужно было использовать состояние в выводе, чтобы потом не писать что ты думал?
код сразу же проспится если
Очередная демагогия "а вот если бы". Я предоставил код который куда проще и без классов. Могу предоставить еще хоть с состоянием, хоть с чем. Перепиши свой код так, чтоб работал "как ты думаешь", я скину аналог.
Лишний мусор только просьба удалить и сделать его минималистичным.
Хорошо, вот модифицированный вариант https://tinyurl.com/3yt8an7k
Я так понимаю вы слились? Я так и думал.
Код
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 только явно и не типобезопасно.
Это вообще ограничение языка, были бы функции - использовал бы их. К этому придираться - это уже видимо совсем не к чему.
Ну так напишите решение на С где нет богомерзких методов, или выберите чисто функциональный язык типа хаскеля, зачем вам этот мерзкий тайпскрипт у которого есть классы и методы. Или с тем же успехом возьмите С++ и перепишите код на ++ без использования классов, ну это уже будет С.
Кстати это будет идеально, покажите что ваш подход лучше работает чем ООП подход в рамках одного языка. Можете любой язык взять. А то сравниваем компилируемый язык с интерпретируемым как-то неправильно.
Ну супер, а потом сопостовлять стэйт и функции которые нужны?
Еще раз, никаких проблем ни с инкапсуляцией, ни с типами там нет. И можно реализовать и с замыканием, и в отдельном фале без замыканий, хоть в ПП, хоть в ФП.
нет вы облажались, потому что исходный инстансер работал со строками и в принципе у него другая логика работы была
вы в своём же примере накосячили
Посмотрите на мое сообщение что вы сами скинули - я как раз меняю тип с массива строк на массив чисел. Так что никаких косяков и проблем с типобезопасностью там нет вообще. Боитесь признать что в вашем любимом С++ такое невозможно сделать?)
Вы используете объект с методами, чтобы зарегистрировать тулу. Это же ООП.
Понятие ООП я довольно четко прописал в статье. Классы не используются.
А в коде используется просто словарь с функциями.
Да кстати, забыл добавить, что произойдёт с вашей системой если сделать например так
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)
Вы код сам скиньте, что сравниваете, а то непонятно о чем речь.
Ну вы же не тупой, должны были заметить единственную ссылку от michael_v89 https://www.mycompiler.io/view/LZT9EFBmlWr
Ок пример выше, тула может иметь состояние (настройки и пр) и может меняться при пикинге (например выделение объектов) очень интересно как это выглядит в ФП с сильной имутабельностью.
Это пользовательский код или код редактора? не вижу чтобы 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), второй инструмент создаёт новые объекты относительно координаты. Все настройки инструментов инкапсулироованны в самих инструментах. И представьте что подобных инструментов сотни (вообще их количество не детерминировано), и их поведение может отличатся кардинально. Как это реализовать в ФП учитывая что инструмент имеет изменяемое состояние, и в процессе использования может менять состояние чего угодно (в рамках предоставленного АПИ редактора)
Абсолютно совершенно так же как работают любые микросервисные системы.
Вместо спагетти из наследуемых классов, вместе меняющих shared state - детерминированный АПИ системы плагинов, который получает от плагина команды и выполняет их.
В моей реализации плагин пошлет дтошку "создать новый тридэ обьект там-то и там-то" в основную систему в нужный момент в ответ на такую же дтошку "мышкой нажали вот в этих координатах, когда был активен инструмент Х (относящийся к плагину)". Возможно по дороге он еще дернет пару апишек основной системы чтбы узнать свои настройки.
Надеюсь когда-нибудь вы изобретёте реактивное программирование.
Ну поднимать микросервисы ради такого, это мощно, и конечно же это будет работать быстрее и это будет проще поддерживать, правда ведь, правда?
Я не спорю с тем что любую систему можно написать в любой парадигме, вопрос в цене, цена продумывания, цена расширения возможностей, цена поддержки, цена производительности. Я не считаю что фп плохое (в случае автора конечно очевидно что это не фп а пп) как и любая другая парадигма, и думаю что все они полезны в своих сферах, я лишь спорю с автором что ООП это плохо.
Выше по ссылке я накидал пример минут за 10-15 сколько вам придётся потратить времени чтобы сделать тоже самое на микросервисах?
Насколько удобней будет пользователю расширять это? В моём решении всего лишь унаследоваться от интерфейса и переопределить метод.
Микросервисы мощный и очень полезный инструмент, но нужен ли он тут? Я ещё не видел 3D редакторов которые использовали бы микросервисы для плагинов.
Честно говоря ничего не понял, можно комментарии написать? (в фп языках вообще не силён) это клиентский код или код редактора, или и то и то?
Простите но всё равно не могу осилить код, слишком инопланетный для меня.
слишком инопланетный для меня
Это было понятно с самого начала... Увы, чтобы понять ответ, надо знать хотя бы часть его.
@IUIUIUIUIUIUIUI - это к нашей дискуссии про нужность высшей математики и образования вообще. Вот гражданин, образованный от С++ до С++. Ну и что ты ему объяснишь?
Пока у него не будет определённой базы, он даже не поймёт твои объяснения, считая их полной чепухой. Задав свой вопрос он не знает половины ответа => полный ответ пролетает мимо ушей.
Ну почему чепухой? Я так не считаю, просто из-за того что это совершенно другая сфера в которой у меня нет опыта я и не могу понять этот код, однако чепухой не считаю.
Так тут просто синтаксис очень отличающийся от си-подобных языков, поэтому и ничего не понятно. При чем тут математика?
я вот тоже не силен в вашем языке, но мне тут видится ООП - есть и класс и его инстансы, с переопределенными методами.
Можете по простому объяснить - чем это от явы отличается? кроме синтаксиса
Это тоже ООП только с системой типов без подтипирования =)
Там тонкие различия. Так-то с высоты птичьего полёта вся 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 написан.
А про gcc совсем ничего придумать не смогли? Ну вы постарайтесь, что ли.
Компиляторы крайне неудобно писать на языках без ADT и сборщика мусора. Поэтому спор C vs C++ тут пролетает - оба хуже.
Это вы теперь будете утверждать, что любой большой/успешный проект использует ООП, просто потому, что для вас ООП это не технологии, а показатель размера/успеха?
Детсадовский наброс, старайтесь лучше.
Если вы не можете запустить КДЕ на 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, алгебраических типов данных (на них хорошо писать аналоги конечных автоматов). Эрланговщина с её отказоустойчивостью вряд ли помешает.
Scala - отличный язык: хочешь объектов с наследованием - пожалуйста, хочешь функционально - пожалуйста, хочешь смешать всё это вместе - пожалуйста.
... отказывайтесь от классов если есть такая возможность (TypeScript, Python) ...
А что делать если заставляют использовать ООП на работе?
Не, ну не худшее… худшее — это таки «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 как раз ООП.
Как это реализовано: это уже детали. Вы же говорите вообще про ООП, что оно плохо. А тут вполне себе оно и есть. По факту.
И ФП... вы как-то резко пошли против принципа наименьшего удивления и применили термин "функциональное программирование" к тому, что обычно именуется "процедурное программирование".
Именно, по сути структура с указателями на функции является декларацией интерфейса, и через такой подход реализуется полиморфизм.
Вотсапп же на эрланге написан, который:
ParadigmsMulti-paradigm: concurrent, functional, object oriented
Lean4 видели? Лучше агды в плане автоматизации (и не только). Не могу сравнить с .. rocq.
Холивары наше все.
Для каждой задачи подходит лучше определенный стек, но нет языка решающего... Легко решающего любые задачи, всегда какие то минусы. Поэтому выбирается язык оптимальный в плане большинства задач и удобный большинству разрабов. Все же знают почему пиндосы на маках работают в основном? Так и тут - массы обучены попсе и двигают ее не желая изучать альтернативы или не имея возможности убедить команду. Яркий пример про раст в линуксе.
Чем ругаться и отстаивать - создали бы список задач и стека под них с приоритетами, что бы по таблице можно было выбрать, понять где будет просадка, что вынуть в микросервисы и реализовать на другом языке
Это был бы труд для потомков и современников. А этот срач к истине не приведет
Если задаче 49 лет - ее еще лет 20 назад нужно было отрефакторить и переписать. Как минимум по соображениям ИБ и отсутствию поддержки новых инструкций процессоров и многопоточности. Ставьте реальные цифры что бы не получать такие замечания.
Легаси возрастом в десятилетия - это как раз реальность. Эти теоретические "нужно было переписать" разбиваются о подсчёт затрат на переписывание.
Легаси возрастом в десятилетия - это как раз реальность. Теоретические "нужно было переписать" разбиваются о подсчёт затрат на переписывание.
Вы с такими идеями сходите в крупнейшие авиакомпании и банки США, посмотрим что вам скажут. :) Там 40-летний легаси с которым работают через 5 слоев эмуляторов терминалов это абсолютная норма.
Как обычно - все ищут оправдания своей безынициативности, лени и неумению преподнести важность задачи. Не нужно стесняться, просто скажите "мне лень убеждать руководство, мне лень заниматься этой задачей, я не сумел объяснить что это критически важно и правильно". К чему скрывать свой недостаток за словами "руководство не разрешает". Не они не разрешают, а вы не продавили. Тут большинство таких "если пнут, полечу" - вас не осудят, это нормально для рядового работяги. Руководители же думают иначе
А это проблема работяг или руководителей?
Сразу видно, с вас не трясли деньги. Причем и бизнес, и IT. Можно сколько угодно убеждать в важности задачи, но если денег нет, то тут или закрывать тему, или менять работу.
Я знаю что такое бюджет, целесообразность и прочее. И если речь про код 2005-2010 года - скорее всего нецелесообразно. Но ТС сам упомянул 49 лет, а это на минуточку код 76 года. Поверьте - если он не соврал то компания тратит огромные усилия что бы "это" вообще запускалось и уже что раз дешевле было переписать. Например до 90х годов дата элементарно не позволяла перейти в 21 век, так как экономили каждый байт Как решили интересно, и за сколько.
Но учитывая что ТС не сообщил ни язык, ни платформу (может спец военная\космо техника и Go\Rast не понимает) - скорее всего в его 49 годах лишняя вторая цифра.
А я смотрю вы один из тех кто вообще не понимает что переписать 40 лет отлаженного и стабильно работающего кода банковского ядра даже для компании из SP100 по цене, сложности и количеству простоя и убытков из-за неминуемых последующих багов и ошибок практически равноценно слову "невозможно".
Возможно вы когда-нибудь вырастите до настоящего разработчика из человека, которому лишь бы в песочнице поковыряться с новыми игрушками, и будете видеть глобальную картину.
Так ведь по заголовку функции должно быть понятно, что ей надо давать. Разве нет? Или предполагается подсовывать функции int, float, string в разных комбинациях и надеяться что функция выдаст правильный ответ?
в примере про полиморфизм? я бы тамм вообще дженерики применил наверное. соотвественно провел бы родство в каве а плюсы использую препроцессор вроде как.
пример на ТС выглядит какт о не слишком уместно вообще...
Даже создатель Java вскоре признал, что добавление классов было ошибкой.
Пруфы-то будут? Было дело, что Тони Хоар признавал, что введение нулл-ссылок, а не классов, было "ошибкой на миллиард долларов".
В сети очень много статей находится по его (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 и определенный опыт разработки (меритократия). Но на хабре так нельзя - здесь правит демократия (или охлократия).
Ну конечно это человечество глупое, а не вы опростались.
То, что обсуждение будет неконструктивно, я знал изначально
Ого, вот это прозорливость: знать, что обсуждение будет неконструктивным, строча пост с бредовым посылом, изобилующий демагогией и агрессией.
"Я знал, что на гулянке будет драка, ещё когда шёл туда с желанием подраться"
То есть я должен был сначала объявить, что мой опыт программирования перевалил за полвека, а уже потом комментировать? Перечислять языки и архитектуры компьютеров надо?
А мерит то кто будет оценивать, демократишки эти, или полу-боги полу-раки?
Чел, тебе не хвает когнитивных способностей чтобы разобраться в порядке вызовов конструкторов и ты при этом пишешь про глупое человечество. Это очень смешно.
человечество не глупое, просто ленивое
ели классы помогают снизить ментальную сложность восприятия и проектирования, то они будут востребованы
Легендарный "уровень дискуссии в Восточной Европе" :)
Есть у нас такие, им скрам методология не позволяет получать критику. Прям в слезы если скажешь что делают фигню
Соглашусь с тем, что в современных тенденциях часто есть излишний перекос в сторону ООП. Но что ООП зло - категорически не согласен. Каждый инструмент хорош для своих задач.
Конструктивно было бы привести пример с кодом, где ФП проигрывает ООП. Если таких примеров не существует, то не соглашусь с вами.
Может скинете небольшой кусочек, по которому можно оценить преимущества ООП? Раз уж вы это утверждаете.
Преимущества ООП раскрываются в больших объёмах кода, вы то ли не знаете этого, то ли намеренно корчите непонимающего, в любом случае это многое о вас говорит.
Я не понимаю, зачем нужно выбирать что-то одно.
Я тоже. Поэтому не пишу исключительно в ООП-парадигме, а выбираю инструменты под задачу.
Рельсы это Ruby on Rails? Но рельсы это инструмент. А продукт, который написан на рельсах нельзя написать на ФП?
Пример - ядро Linux, где на сишке накостылен ООП. Идите объясните им, что они дураки и ничего не понимают в программировании.
В GTK тоже зачем-то свой ООП накостылили, вот идиёты...
Вот сейчас не понял. Что значит на си ООП написан??
Не на Си ООП написан, а код на Си написан в ООП-парадигме.
Есть паттерн макросов, метапрограмиирования если позволите.. х-Макрос называют.
Он позволяет генерировать списки, знаете? Это легко даже без гцц позволит вам передать контекст в функции первым аргументом. При необходимости можно использовать просто для енум и массивов связанных с ним, но генерируя таким образом огромные свич кейзы легко можно связать все функции с указателями внутри структур, будь она композицией или просто оберткой вашего объекта...
А у них в Линуксе гцц позволяет писать замыкания через вложенные функции и лямбды, и лично у меня этот подход просто расцветает
Не для всего подряд а там где нужно, колбеки там всякие, события в петлях

Зы
.Методы, делегаты, инкапсуляции, виртуализации через таблицы- 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-илетний супер-дупер-мега-гига-убер-профи не смог.
Если хотелось реализовать мемоизированное вычисляемое состояние, то это делается элементарно в каком нибудь zustand / redux через reselect, если устраивает иммутабельный стор, если не устраивает есть и мутабельные сторы в парадигме ФП.
Более того, можно хоть на ивент еммитерах это реализовать если нужна супер произвотидельность.
Но самое смешное что даже RX из ваших примеров можно использовать в парадигме ФП, и первый кусок кода в ней и написан, если не считать создание BehaviorSubject через new, который мог бы создаваться через функцию make*.
Вот код что сгенерировал GPT, я не проверял но плюс минус то же самое но без классов - меньше, понятнее и без проблем из статьи:
const mem = pipe(distinctUntilChanged(), debounce(0), shareReplay(1))
const makeToys = () => {
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 filteredToys = filter.pipe(
switchMap(currentFilter => toys.pipe(
map(toysArray => toysArray.filter(currentFilter))
)),
mem
);
return {
toys,
filteredToys,
setFilter: (newFilter) => filterSource.next(newFilter)
}
}Ваш код работает катастрофически медленно, выполняя кучу лишней работы при изменении полей, от которых результат не зависит.
Он работает так же [плохо] как и в вашем примере.
Если это был типа "плохой" пример, то надо учиться яснее общаться. Второй ваш пример это вообще непонятно что на вашем фреймворке, в котором 1) я разбираться не имею никакого желания 2) вообще не ясно как массив редактируется и как используется, и почему у игрушки зачем то есть count().
Если вы топите за мутабельное состояние [от которого проблем часто больше, при схожей производительности], то вот простейший пример вообще без фреймворков:
type Toy = {
count: 0
}
type State = {
toys: Toy[],
filteredToys: Toy[],
filter: (x) => boolean
}
const makeToysStore = () => {
const state: State = {
toys: [],
filter: (x) => x.count > 0
}
const setFilter = (filter: State['filter']) => {
state.filter = filter
state.filteredToys = state.toys.filter(filter)
EventEmitter.emit('toysStoreChanged', ['filter', 'filteredToys'])
}
return {
getState: () => state,
setFilter
}
}
export const toysStore = makeToysStore()
Можно было бы вынести повторяющиеся паттерны в отдельный фреймворк, либо использовать готовые решения, но суть в том, что это все прекрасно реализуется в ФП.
Видишь, опять твое 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() у инстанса не думая о том, какой зоопарк кодеков у вас на самом деле.
А что вы делаете в ФП? Тоже тогда с кодом, пожалуйста.
полиморфизм не обязательно реализовывать через классы
В коде ключевые слова class, instance... Ну допустим это другое. Я не против что это возможно сделать на ФП. Но это будет очень сложно и неудобно. Так-то и fizzbuzz можно на ООП нафигачить, как в той известной шутке, но лучше ли это процедурного стиля?
Где у вас вот в этом коде будут вызовы для кодирования данным конкретным кодеком, где будет состояние is_fallback_? А где состояние каждого отдельного кодека? Можете его допилить хотя бы до того же уровня абстракции что и у меня, с функциональность fallbackEncoder? А то получается, что можно реализовать пристейший тривиальнейший пример и даже не так страшно выглядит. А вот уже в реальном проекте, где классов больше двух и всякие сложности бывают - получается очень неудобно и неподдерживаемо.
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% ни в одном языке, но функциональные языки в основном их покрывают:
Функции — first class values
Развитые строгие статические системы типов
Неизменяемые структуры данных
Контроль эффектов
«Декларативный» стиль
И, наверное, я что-то забыл.
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_, если произойдет ошибка. Вы этот код опустили. Оно вообще будет работать в вашем подходе?
Здесь полный код
Лаконично
Ага, как же. У вас код не эквивалентный - установки 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++, и много копипасты.
установки is_fallback нет
В вашем последнем куске кода тоже нет.
Как у вас сделать вот такую конструкцию?
const makeEncoder = (encoding: SoftwareEncoding | HardwareEncoding | FallbackEncoding): Encoder => {
switch (encoding) {
case 'vp9f': return makeSoftwareFallbackEncoder('vp9h', 'vp9s');
case 'vp8f': return makeSoftwareFallbackEncoder('vp8h', 'vp8s');
case 'vp9s': return makeVp9SWEncoder()
case 'vp8s': return makeVp8SWEncoder()
case 'vp9h': return makeVp9HWEncoder()
case 'vp8h': return makeVp8HWEncoder()
// ...
}
}
const makeSoftwareFallbackEncoder = (primary: HardwareEncoding, fallback: SoftwareEncoding): Encoder => {
let encoder = makeEncoder(primary)
let isFallback = false
return () => {
encode: (frame) => {
if (isFallback) {
return encoder.encode(frame)
}
try {
return encoder.encode(frame)
} catch {
encoder = makeEncoder(fallback)
isFallback = true
return encoder.encode(frame)
}
},
// ...
}
}вы реализовали ООП вручную
Я все проблемы ООП, перечисленные в статье, не реализовывал в своих примерах - и их там нет. А если, как вы заявляете, код очень похож, но в одном из вариантов сильно меньше проблем чем во втором - значит он лучше.
В вашем последнем куске кода тоже нет.
В каком куске кода? Похоже вы перепутали собеседников.
У другого вашего собеседника, насколько я вижу, примеры кода не независимые, а являются часть одной логики и дополняют друг друга. Он написал "К моему коду выше добавляем".
makeSoftwareFallbackEncoder
20 строк кода
В исходном примере аналогичная функциональность занимает аналогичное количество строк кода. То есть никакой лаконичности у вашего кода, о которой вы говорили, нет.
Кроме того, этот код нельзя использовать с тем, который вы писали в предыдущем комментарии, то есть тот код тоже надо переписывать. Как я и сказал, одни увиливания.
Я все проблемы ООП, перечисленные в статье, не реализовывал в своих примерах - и их там нет.
В вашем коде, который имитирует ООП, есть все проблемы ООП, которые вы перечислили в статье.
Я бы вам ткнул пальцем, но вы отказываетесь писать код примера полностью. Видимо сами понимаете, что тогда их будет видно.
но в одном из вариантов сильно меньше проблем чем во втором - значит он лучше
Правильно. А если больше, то хуже. В вашем коде проблем больше.
код тоже надо переписывать
То что можно функцию разбить на несколько функций это называется обычный рефакторинг. Если код слишком простой - нет смысла городить абстракции, если начинает усложняться то смысл появляется.
Правильно. А если больше, то хуже. В вашем коде проблем больше.
Я бы вам ткнул пальцем,
Ключевые слова "я бы". Я думаю можно этими двумя словами подытожить все потуги ООПшников))
То что можно функцию разбить на несколько функций это называется обычный рефакторинг.
Мне без разницы, как это называется. Вы оба раза не привели полный код примера, но рассказываете сказки про его лаконичность.
Ключевые слова "я бы".
Вас не смущает, что после этих слов, которые вы вырвали из контекста, идет описание условия, которое зависит от вас? И что сама фраза касается объяснения вам некоторой точки зрения?
То есть у вас получается так:
"Я прошу показать, почему ООП лучше моего подхода, но все потуги ООПшников это сделать разбиваются о мое нежелание приводить полный код примера с моим подходом".
И вы еще этим гордитесь?)) Это хорошо показывает, что нет смысла ожидать от вас нормальной дискуссии.
Какой кусок кода нужно еще привести, чтобы вы смогли увидеть проблемы?)
И где ваш полный пример этого кода?)
И где ваш полный пример этого кода?
Ваш собеседник вам уже его привел, зачем я должен его еще раз приводить?
https://habr.com/ru/articles/885980/comments/#comment_27974912
https://habr.com/ru/articles/885980/comments/#comment_27978858
Какой кусок кода нужно еще привести, чтобы вы смогли увидеть проблемы
Объясняю еще раз. Приводить надо не "кусок кода", а полный пример кода. Полный. Полностью. Весь код. Который реализует всю функциональность, которая есть в исходном примере. Не в виде "encodeXxx", а с теми же названиями, которые были в исходном примере.
Скопируйте код из обоих комментариев в один файл (ссылки в предыдущем абзаце); перепишите весь этот код на JavaScript в вашем стиле с сохранением всех имен и функциональности, то есть без замен на "encodeXxx"; добавьте тот вариант использования, который я написал в своем комменте, на который вы сегодня ответили, потому что ООП и используют именно для поддерживаемости при новых требованиях; и выложите то, что получилось. Потом будем сравнивать, насколько он лаконичнее исходного, и какие там проблемы есть на данный момент или могут появиться при изменениях требований.
Я это уже писал 28 февраля в этом комменте, на который вы сегодня ответили. Сколько раз вам надо повторять одно и то же, чтобы вы поняли?
Вы сами скинули ссылки на два куска кода, и плюс еще просите учесть свой кусок кода. У меня аналогичные куски кода.
Приведите сами ВЕСЬ код а не несколько кусков, потом поговорим.
Я вам уже объяснил, что эти части дополняют друг друга, их можно использовать совместно, а у вас для использования второй части надо переписывать первую. Видимо вы действительно с первого раза не понимаете, вам надо по 10 раз объяснять. Или просто притворяетесь и валяете дурака.
Ладно, вот полный код, 89 строк.
Код
class Encoder {
public:
virtual Bitstream Encode(VideoFrame frame) = 0;
virtual void RequestKeyFrame() = 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();
}
void RequestKeyFrame() {
if (is_fallback_) sw_encoder_->RequestKeyFrame();
else hw_encoder_->RequestKeyFrame();
}
private:
bool is_fallback_ = false;
Encoder * sw_encoder_;
Encoder * hw_encoder_;
};
Encoder* CreateEncoder(CodecType codec) {
switch (codec) {
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();
case vp8: return new VP8SWEncoder();
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;
}
}
}
Какой то детский сад а не код)) Даже не буду рефакторить, выложу то же самое (78 строк).
type Encoder = {
encode: (frame: VideoFrame) => BitStream;
requestKeyFrame?: () => void;
};
const makeVp9SWEncoder = (): Encoder => ({
encode: (frame) => {
// кодируем через libvpx
},
});
const makeVp9HWEncoder = (): Encoder => ({
encode: (frame) => {
// Вызываем функции ОС чтобы драйвер видеокарты что-то закодировал
},
});
const makeSoftwareFallbackEncoder = (swEncoder: Encoder, hwEncoder: Encoder): Encoder => {
let isFallback = false;
return {
encode: (frame) => {
if (!isFallback) {
const encoded = hwEncoder.encode(frame);
if (!encoded.isError) return encoded;
isFallback = true;
}
return swEncoder.encode(frame);
},
requestKeyFrame: () => {
if (isFallback) swEncoder.requestKeyFrame?.();
else hwEncoder.requestKeyFrame?.();
}
};
};
const makeEncoder = (codec: Codec): Encoder => {
switch (codec) {
case 'vp9f': return makeSoftwareFallbackEncoder(
makeVp9SWEncoder(), makeVp9HWEncoder()
);
case 'vp8f': return makeSoftwareFallbackEncoder(
makeVp9SWEncoder(), makeVp9HWEncoder()
);
case 'vp9s': return makeVp9SWEncoder();
case 'vp9h': return makeVp9HWEncoder();
case 'vp8h': return makeVp9HWEncoder();
case 'vp8': return makeVp9SWEncoder();
case 'vp9': return makeVp9SWEncoder();
case 'h264': return makeVp9HWEncoder();
}
};
const main = () => {
// ...
const codecs = getCodecs();
const encoders = codecs.map(makeEncoder)
while (true) {
const event = getEvent();
if (!event) break;
switch (event.type) {
case 'gotFrame': {
for (const encoder of encoders) {
encoder.encode(event.getFrame());
}
break;
}
case 'requestedKeyFrame': {
for (const encoder of encoders) {
encoder.requestKeyFrame?.();
}
break;
}
}
}
};78 строк
Все различия связаны с синтаксисом языков JavaScript/CPP и не связаны с вашим подходом. Также вы пропустили одну строку.
То есть лаконичности, про которую вы говорили, с использованием процедур вместо классов нет.
Diff
- public:
- public:
- public:
// SoftwareFallbackEncoder
- private:
- Encoder * sw_encoder_;
- Encoder * hw_encoder_;
-
- bool is_fallback_ = false;
-
- public:
- SoftwareFallbackEncoder(Encoder sw, Encoder hw): sw_encoder_(sw), hw_encoder_(hw) {}
+ let isFallback = false;
- case vp8s: return new Vp8SWEncoder();
+ У вас эта строка пропущена
- vector<Encoder> encoders;
- for (auto codec : codecs) {
- encoders.push_back(CreateEncoder(codec))
- }
+ const encoders = codecs.map(makeEncoder)
Также в вашем коде можно отметить большую вложенность элементов. Это тоже не выглядит более лаконично.
// js
return {
encode: (frame) => {
}
}
// cpp
Encode(frame: Frame) {
}
Теперь значит про проблемы ООП.
У вас функции encode и requestKeyFrame из глобальных процедур, за которые вы так выступали, стали методами объекта. Поэтому для них стали актуальны проблемы, которые вы указали в статье.
"метод намертво приколочен к типу своего неявного аргумента — this"
Методы encode и requestKeyFrame в вашей реализации тоже приколочены к this. Например если вы захотите вызывать один метод из другого, вы будете использовать this.
"Он зависит не от интерфейса, а от конкретного класса."
У вас эти методы зависят даже не от класса, а вообще от конкретного объекта.
"метод требует для работы не только данные и методы, что ему нужны, но и те что не нужны, но есть в классе User - то есть абсолютно все поля и методы этого класса"
У вас то же самое, методы encode и requestKeyFrame "требуют для работы" все методы, которые есть в объекте где они объявлены.
"нельзя использовать метод без создания экземпляра этого класса или его потомка"
У вас аналогично, нельзя использовать метод без создания объекта где он содержится.
"Невозможность обработки ситуации, когда user null или undefined в самом методе"
Сможете обработать в ваших методах ситуацию, когда encoder null или undefined? Я в этом сомневаюсь.
let encoder: Encoder | null
encoder.requestKeyFrame() // Ошибка: Uncaught TypeError: can't access property "requestKeyFrame", encoder is undefined
"нельзя отнаследовать определенные поля или методы — только целиком весь класс"
Нельзя отнаследовать или переиспользовать другим способом один метод encode. Чтобы его вызывать, нужно получить целиком объект из функции makeVp9SWEncoder, в котором будут оба метода.
"Используем сырые данные, без лишних преобразований."
"getArea(shape)"
Если у вас будут сырые данные, представляющие стейт кодеков (который вы ранее обозначили как XxxState/YyyState), вы не сможете их использовать без лишних преобразований. Вам надо будет для каждого стейта вызвать одну из функций makeVp8SWEncoder / makeVp9SWEncoder /makeSoftwareFallbackEncoder в виде "makeVp9SWEncoder(rawCodecState)", то есть сделать фабрику для создания объектов кодеков из сырых данных.
Все различия связаны с синтаксисом языков JavaScript/CPP
Нет, в JS тоже есть классы и вся эта ерундистика. Я использовал более лаконичный и простой подход без лишних символов. И по сторчкам, и в целом по символам вышло меньше. Даже на специально выбранном вами примере и с точно таким же подходом без особого рефакторинг.
Методы encode и requestKeyFrame в вашей реализации тоже приколочены к this. Например если вы захотите вызывать один метод из другого, вы будете использовать this
Они вообще никуда не приколочены и там нет ни одного символа this.
Более того у стрелочных функций что я использую в принципе нет this.
Этот пример полная копия вашего, только без классов.
Эти функции можно без проблем вынести в процедуры если возникнет такая необходимость, например:
// Аргументы только те, что используются, ничего лишнего
const encodeVp9S = (frame: Frame) => {
// ...
}
const makeVp9SWEncoder = (): Encoder => ({
encode: encodeVp9S
});
У вас эти методы зависят даже не от класса, а вообще от конкретного объекта.
Ограничений здесь нет, см. пред. пример.
У вас то же самое, методы encode и requestKeyFrame "требуют для работы" все методы, которые есть в объекте где они объявлены.
Опять ошибка - где они это требуют? У них даже ссылки нет друг на друга))
У вас аналогично, нельзя использовать метод без создания объекта где он содержится.
И тут ошибка - если нужно то можно - см. пред. пример.
Сможете обработать в ваших методах ситуацию, когда encoder null или undefined? Я в этом сомневаюсь.
const encodeVp9S = (frame?: Frame) => {
if (!frame) return null
// ...
}
encoder?.encode()
encoder?.requestFrame?.()А в замыкании ситуакция когда оно null невозможна.
Итого, написанное калькой ваше же решение, которое лично я бы писал по другому, более лаконичное и не имеет никаких ограничений.
Я использовал более лаконичный и простой подход без лишних символов.
и в целом по символам вышло меньше
Объясняю еще раз то же самое, что было написано в моем предыдущем комментарии. Исходный пример написан на C++, а вы использовали JavaScript. Все различия связаны только с разным синтаксисом языков, а не с тем, что вы не использовали классы. Я привел diff с доказательством.
Вот тот же код на TypeScript с классами и объектами в стиле ООП. Он занимает ровно те же 78 строк. Значит различие в количестве строк не связано с использованием ООП. Более того, в нем на 10 символов меньше, чем в вашем.
TypeScript
interface Encoder {
Encode(frame: VideoFrame): Bitstream;
RequestKeyFrame(): void;
};
class Vp9SWEncoder implements Encoder {
Encode(frame) {
// кодируем через libvpx
}
};
class Vp9HWEncoder implements Encoder {
Encode(frame) {
// Вызываем функции ОС чтобы драйвер видеокарты что-то закодировал
}
};
class SoftwareFallbackEncoder implements Encoder {
private isFallback = false;
constructor(private swEncoder: Encoder, private hwEncoder: Encoder) {}
Encode(frame) {
if (!this.isFallback) {
const encoded = this.hwEncoder.Encode(frame);
if (!encoded.isError) return encoded;
this.isFallback = true;
}
return this.swEncoder.Encode(frame);
}
RequestKeyFrame() {
if (this.isFallback) this.swEncoder.RequestKeyFrame();
else this.hwEncoder.RequestKeyFrame();
}
};
function makeEncoder(codec: Codec): Encoder {
switch (codec) {
case 'vp9f': return new SoftwareFallbackEncoder(
new Vp9SWEncoder(), new Vp9HWEncoder()
);
case 'vp8f': return new SoftwareFallbackEncoder(
new Vp8SWEncoder(), new Vp8HWEncoder()
);
case 'vp9s': return new Vp9SWEncoder();
case 'vp9h': return new Vp9HWEncoder();
case 'vp8h': return new Vp8HWEncoder();
case 'vp8': return new Vp8SWEncoder();
case 'vp9': return new Vp9SWEncoder();
case 'h264': return new H264HWEncoder();
}
};
const main = () => {
// ...
const codecs = GetCodecs();
const encoders = codecs.map(makeEncoder)
while (true) {
const event = getEvent();
if (!event) break;
switch (event.Type()) {
case 'gotFrame': {
for (const encoder of encoders) {
encoder.encode(event.getFrame());
}
break;
}
case 'requestedKeyFrame': {
for (const encoder of encoders) {
encoder.requestKeyFrame();
}
break;
}
}
}
};
написанное калькой ваше же решение более лаконичное
Вы это для самоуспокоения что ли повторяете?) Ваш пример не является более лаконичным, я уже несколько раз это объяснил с доказательствами.
Более того у стрелочных функций что я использую в принципе нет this.
А, то есть если надо будет вызвать encode из requestKeyFrame, надо будет городить третью функцию до return и вызывать ее из обоих? А говорите, лаконично.
Получается, ваш код содержит меньше возможностей, чем был с ООП.
Эти функции можно без проблем вынести в процедуры если возникнет такая необходимость
Вы согласились написать полный пример для демонстрации вашего процедурного подхода? Согласились.
В вашем полном примере они вынесены? Нет.
Опять начинаются какие-то увиливания "ну там можно по-другому переписать". Смысл этого примера был в том, чтобы показать полный финальный код. Тут даже смысла обсуждать нет, вы неспособны вести нормальную дискуссию.
Ну попробуйте заменить swEncoder.requestKeyFrame() на вызов процедуры. У вас это не получится. В этом был весь смысл примера с кодеками.
У вас даже вынести requestKeyFrame из makeSoftwareFallbackEncoder "без проблем" не получится.
Подробнее
Она использует состояние isFallback, которая у вас локальная переменная, поэтому просто так вынести ее нельзя. Вам придется передавать ее аргументом, как и оба энкодера. Где уж тут лаконичность. И строк кода у вас уже будет не 78.
const msfe_requestKeyFrame(swEncoder, hwEncoder, isFallback) => {
if (isFallback) swEncoder.requestKeyFrame();
else hwEncoder.requestKeyFrame();
}
makeSoftwareFallbackEncoder(swEncoder, hwEncoder) {
let isFallback = false;
requestKeyFrame: () => msfe_requestKeyFrame(swEncoder, hwEncoder, isFallback)
}
Обратите внимание, внутри makeSoftwareFallbackEncoder стрелочная функция осталась, то есть одна функция вызывает другую. Глобальные функции можно вызывать и с ООП. Если вы это не считаете проблемой в своем коде, тогда и в ООП это не является проблемой.
Опять ошибка - где они это требуют? У них даже ссылки нет друг на друга))
Там же, где их по вашим словам требует метод User.getDisplayName() из вашего примера в статье. У него тоже нет ссылок на другие методы, но вы сказали, что он их требует. Вы уж определитесь, либо в обоих случаях требуют, либо в обоих не требуют, так как в обоих случаях это объект с методами.
encoder?.encode()
Это можно сделать и с ООП, но вы в статье все равно это указали как проблему ООП. Значит и вашем коде она есть. Вы уж определитесь, такой вызов это проблема или нет.
Он занимает ровно те же 78 строк
Удобно не реализовывать полностью интерфейс и конструктор сделать пустым и однострочным, что еще удалили?
И это на элементарном примере, заточенном для "У вас это не получится. В этом был весь смысл примера с кодеками."
Вы согласились написать полный пример для демонстрации вашего процедурного подхода? Согласились.
Я согласился переписать ваш код на ФП, что без проблем сделал. Про процедурный подход это вообще ваша выдумка - в статье ясно говорится в начале что такое ФП и ПП, и статья про ФП.
Ну попробуйте заменить
swEncoder.requestKeyFrame()на вызов процедуры. У вас это не получится. В этом был весь смысл примера с кодеками.
Я ее заменил на вызов функции, без классов и без проблем из статьи. Хотя даже и на вызов процедуры заменить не проблема, и самое смешное что вы далее сами это делаете. Не зря статью читали (с).
Глобальные функции можно вызывать и с ООП
Во-первых функция может быть необязательно глобальная, а создана в функции конструкторе или еще где то.
Во-вторых, я рад что вы в итоге пришли к ФП и когда нужно сделать что то гибко и просто - будете его использовать, вызывая глобальные функции. Жаль только не во всех языках это можно делать, и придется создавать нетестируемые статические классы.
User.getDisplayName() из вашего примера в статье. У него тоже нет ссылок на другие методы,
Вы видимо таки не поняли о чем речь, попробую объяснить еще проще: this - это объект класса где есть все из этого класса. А значит метод жестов завязан на тип этого класса, и его нельзя переиспользовать для данных, у которых нет всего, что есть в this. То есть если в User есть метод goFckYourself, то чтобы переисолзовать getDisplayName из User нужно будет добавить в мои данные, например Dog, этот же метод, иначе никак. Более того, единственный способ их добавить это отнаследовать Doc от User, добавив таки эти методы. Надеюсь теперь понятно, если нет - тут уже ничем не помогу.
Это можно сделать и с ООП, но вы в статье все равно это указали как проблему ООП.
В статье речь про то, что в методах класса выбора нет, а в ФП выбор есть, и даже пример есть с getDisplayName - советую почитать еще раз.
В итоге ни одно ваше утверждение не доказано, ни про "у вас это не получится", ни про "у метода нет ссылок на другие методы", ни то что можно сделать this опциональным в ООП, и даже переписать на классы JS без читинга ваш собственный, якобы заточенный на превосходство ООП пример.
Полный провал.
Но хотя бы пришли к тому, что в С++ тоже можно использовать процедуры если нужно гибко и просто. Хоть что то хорошее.
PS. Сложность кода оценивается не только строчками, а в том числе количеством символов языка (типа class со всеми вытекающими из них this, наследованием, модификаторами доступа и тп), багоемкостью (типа попытки передать метода класса как параметр, получая вместо this undefined) и прочими проблемами, перечисленными в статье. Тут у вас тоже полный провал.
Удобно не реализовывать полностью интерфейс
Почему это я должен его реализовывать, если у вас реализации этого метода нет? Я реализовал то, что было в исходном примере.
Это у вас неправильно, метод requestKeyFrame должен быть реализован во всех кодеках, поэтому в интерфейсе он должен быть обязательным.
что еще удалили?
Я ничего ниоткуда не удалял. В моем коде есть всё, что было в исходном примере и в вашем.
У вас нормальных аргументов не осталось, и вы начали придумывать то чего нет?
Про процедурный подход это вообще ваша выдумка - в статье ясно говорится
Мне без разницы, что говорится у вас в статье про название подхода. В с общепринятыми определениями этот подход называется процедурный, что вам уже не раз объяснили в комментариях. Вы можете использовать любые выдумки, я буду пользоваться общепринятыми названиями.
я рад что вы в итоге пришли к ФП
У вас неадекватное восприятие реальности. Я не "приходил" ни к настоящему ФП, ни к тому, что вы называете ФП.
В русском языке слова "можно вызвать" в том контексте обозначают просто констатацию факта.
Вы видимо таки не поняли о чем речь, попробую объяснить еще проще:
Вы неправильно понимаете происходящее. Я прекрасно понимаю, о чем у вас речь, пожалуйста учитывайте это во всех будущих комментариях. Если вам кажется, что я что-то не понял, считайте это предположение неверным, и предполагайте, что вы что-то не так поняли в моем комментарии.
this - это объект класса где есть все из этого класса
и его нельзя переиспользовать для данных, у которых нет всего, что есть в this
Вот и результат makeSoftwareFallbackEncoder это объект, где есть все методы, которые там реализованы.
Вот и методы этого объекта нельзя переиспользовать для данных, у которых нет всего, что есть в виде локальных переменных замыкания (таких как isFallback). Вы же в курсе, что в движке JS замыкание с захваченными локальными переменными это объект с полями? Похоже что нет.
и самое смешное что вы далее сами это делаете
Похоже вы что-то не так понимаете. Возможно путаете объявление и вызов. Я нигде не заменял вызов swEncoder.requestKeyFrame() на вызов процедуры/функции.
Я согласился переписать ваш код на ФП, что без проблем сделал.
Не сделали. В исходном примере вызываются методы объектов, в вашем тоже вызываются методы объектов. Заменить их на вызовы функций, то есть без конструкции "переменная-точка-метод", вы не смогли. В статье рекомендуете это делать, а сами не сделали.
Я ее заменил на вызов функции
Это ложь, в вашем коде написано swEncoder.encode(frame) и swEncoder.requestKeyFrame?.(). Это вызовы методов объектов, а не вызовы функций.
Поскольку вы скатились до открытого вранья, я не вижу смысла далее вам отвечать.
Можем вернуться к этому разговору, когда вы приведете код, в котором не будет ни одного вызова методов через точку, как вы и предлагали делать в статье. Именно это я подразумевал в предложении "перепишите этот код на JavaScript в вашем стиле", на которое вы согласились.
Нигде в статье я не предлагал избавиться от замыканий и складывания функций в мапы, хоть и процедурный подход во многих случаях - самый лучший вариант. Это все ваши фантазии. Я предлагал избавиться от классов, на что мне пытались привести примеры якобы нереализуемого кода, включая ваш, но так и не привели ни одного, потому что их не существует. А если такая уродливая конструкция как класс ничем не лучше и обладает множеством недостатков, значит ее быть не должно.
Нигде в статье я не предлагал избавиться от замыканий
Я не говорил, что вы в статье предлагали избавиться от замыканий.
Вы предложили заменить объекты с полями на замыкания с локальными переменными, которые точно такие же объекты с полями.
и складывания функций в мапы
В JS экземпляр класса с методами это и есть мапа с функциями. В статье вы именно предлагали от них отказаться: "Речь пойдет про методы экземпляра".
Я предлагал избавиться от классов
Но сделали их полный аналог в виде пары нетипизированный объект + тип typescript (аналог new SomeClass), методами объекта через точку как в ООП, замыканиями с локальными переменными (аналог полей класса), и функциями makeXXX (аналог конструктора). О чем я вам уже писал.
Классы под капотом именно так и работают, слово "class" это просто синтаксический сахар для того, что вы сделали в своем коде.
и обладает множеством недостатков
Раз это полный аналог, значит и ваш код обладает теми же недостатками.
на что мне пытались привести примеры якобы нереализуемого кода
Никто не говорил про нереализуемый код, вы спорите со своими фантазиями. Все прекрасно знают, что код с классами на C++ компилируется в набор машинных команд без классов.
Я вам об этом писал тут еще полгода назад.
включая ваш, но так и не привели ни одного
Вы в статье говорили, что не надо использовать объекты с методами, но в примере с кодеками не смогли избавиться от объектов с методами. Значит привели. На этом всё.
а теперь представье что у вас мега класс с кодеками будет активным(если хотите чуточку реактивным) и вы загрузите весь класс, отсюда следует надо запускать арбитра, который точно поймёт какой нужен кодек и вызовет только его, и кодеки искать не надо максимум хэш мапу можно завести по имени
(str,*codec) обьекты хранить накладно лучше хранить адреса тоесть чтото ближе к указателям(умным), я знаю что это спорно но обьекты хранить накладно, файл хранить 1 ок, но только 1 и давать на него указатель вот, а так обьекты не то я думаю
Это выглядит и ведет себя в точности как класс в ООП, хоть вы это и как-то по другому называете.
Блин, вся соль в нюансах, в том, как написать минимально геморройно. Так концепции — статическое связывание vs динамическое связывание, известны со времён OS/360, если не раньше.
Пример — хаскельные классы типов похожи на виртуальное наследование в С++, только вызовы перегруженных функций в Хаскеле как правило девиртуализованы, а в С++ — как правило не девиртуализованы. В результате, у вас в хаскеле в 99.99% случаев работает inline, а в С++ — нет.
Для того, что вы написали, совершенно не нужно ООП и классы, а достаточно переменной, значением которой является функция или указатель на неё. Такую VMT даже на ассемблере легко написать.
Для того, чтобы функции содержать своё состояние, тоже не нужен класс, а достаточно указателя на контекст или даже просто статической переменной (в императивной парадигме) или замыкания (в функциональной).
ООП даст вам, по сути, только одно: объект сможет пользоваться реализацией родительского класса, при этом сам не зная, кто его родитель. Насколько это актуально в случае кодеков, мне сложно представить.
Такую VMT даже на ассемблере легко написать.
Вот именно, все можно реализовать в ассемблере. Но говорить, что языки высокого уровня - "худшее" и надо писать все на ассемблере - полнейший же бред, правда?
Для того, чтобы функции содержать своё состояние, тоже не нужен класс, а достаточно указателя на контекст или даже просто статической переменной (в императивной парадигме) или замыкания (в функциональной).
Достаточно. Но удобнее ли? Не создает ли это дополнительных проблем?
ООП даст вам, по сути, только одно
Суть классов - данные хранятся вместе с методами их обработки. И это часто очень удобно.
ООП даст вам, по сути, только одно: объект сможет пользоваться реализацией родительского класса, при этом сам не зная, кто его родитель
В ФП с этим нет проблем - функция может пользоваться другой функцией, не зная конкретно что это за функция.
Что мешает то же самое сделать в ФП?
А приведите там код, как вы в рантайме, в зависимости от пользовательского ввода решаете делать ли просто Encoder или заворавичвать его в DumpingEncoder? Как вы потом его вызываете?
Потом, у вас тут не ООП разве? Классы есть, экземпляры какие-то есть, данные, насколько я понимаю, лежат рядом с функциями где-то. Функции нифига не чистые, или я что-то напутал? Где лежит is_fallback, ведь он же меняется? Или это какой-то стейт неявно передаваемый функции, точно так же как методам класса неявно передается инстанс объекта. В чем тогда вообще разница с ООП?
-- Функция вызывающая полиморфный код
А если надо разделять создание и использование? Вот вы сейчас создаете энкодер, но видео фрейма у вас под рукой нет, вызывать кодирование вы будете потом, в другой часте кода? Или так нельзя и вам придется в другую часто кода вот этот user input тащить?
Вот у вас пример тривиальный, закодировать 3 заранее известных фрейма, а на деле это не так. Они от драйвера камеры приходят в неизвестное вам время. И вам надо в обработчике вот того события, которое не вы вообще вызываете, иметь сконфигурированный вами энкодер.
Внутри SoftwareFallbackEncoder
А где хранится SoftwareFallbackEncoder? Я вот чего не понимаю, у вас в зависимости от пришедших данных, ну или ввода, как в примере, состояние может быть просто vp9sw, может быть SoftwareFallbackEncoder, оборачивающий vp9sw и vp9hw, а может быть frameDumping (который какой-нибудь файл открытый хранит) и при этом оборачивающий или просто vp9sw или опять же FallbackWrapper. Комбинаторный взрыв вариантов типов. Вам надо это состояние для каждого видео потока отдельно где-то держать и в нужную функцию передавать. Как?
Все эффекты (в данном случае доступ к состоянию) явно выражены в типах
Я правильно понимаю, что если у вас там у encoder будет кроме возможности "закодировать фрейм" еще и функциональность "переконфигурировать себя под измененный битрейт", которая вызвыается отдельно от кодировки фрейма, потому что пораждается событями от сети, а не от камеры, то в этом типе тоже будет выражено, что там есть доступ к тому же внутреннему состоянию энкодера?
Это в ООП эквивалентно "вызовы методов объекта имеют доступ к состоянию этого объекта". Очевидная вещь. Возможно, вы в ФП можете чуть более тонко это указывать вроде как какая часть состояния может поменятся, а какая нет. Это просто не так развито в ООП, но в том же с++ можно пометить функцию const, указав в ее сигнатуре, что она стейт не меняет. Теоретически так же можно было бы указывать и про разные части стейта более тонко указывая эффекты доступа к состоянию. Но это не делают, потому что это создает сложности. Инкапсуляция сильно снижает общую сложность, лучше пусть пользователи не знают, что конкретно там внутри и как оно меняется, пусть верят интерфейсу и не ломают голову.
ФП на инкапсуляцию с другой стороны смотрит.
Согласен, что ФП тут дает больше описательной силы для интерфейса. Не согласен, что разница принципиальна. Почему, расписал в ветке ниже: https://habr.com/ru/articles/885980/comments/#comment_27977022
Спасибо, но я все еще не понимаю, а где хранится FallbackState для каждого энкодера в системе? Вот эта mkEncoder она могла вернуть fallback врапер, а могла просто энкодер. Как вы потом ее результат вызваете?
Он хранится для каждого созданного «экземпляра» fallback-энкодера в замыкании соответствующей функции.
Спасибо за ваше терпение. Теперь я понял. В отличии от ООП, где создается "объект" (набор данных и код), в ФП создается "замыкание функции" (код и набор данных). Вместо иерархии классов, описывающих как "объекты" в друг друга вкладываются, тут иерархия типов, описывающая, как "функции" в друг друга вкладываются. Есть такой же полиморфизм, что-то типа наследования (классы и инстансы) и инкапсуляции (можно не смотреть, что там конкретный instance делает, а нужно лишь доверять контракту, описанному в классе).
Т.е. для решения описанной мной задачи используются ровно те же механизмы, на которых основанно ООП (и которые хаятся автором статьи), только названия и синтаксис разные. В качестве бонуса более сильный статический анализ компилятором за счет типов. Минусы, я думаю, тоже есть. Мне больше всего кажется переусложненным синтаксис, но наверно это я просто непривычен.
Интересно, а как это работает, если с конкретным стейтом надо делать несоклько разных манипуляций. Например, с энкодерами - можно попросить его закодировать фрейм, а можно сказать ему, что данные потерялись, надо переконфигурироваться и для следующего фрема сделать key-frame. И эти две операции происходят с разных мест. Тут у вас уже будет несколько функций, работающих с одним и тем же стейтом, но стейт же хранится в замыкании одной фукции. Надо будет функции передавать параметр конкретной операции? Или есть более простой механизм?
Вот, я осознал проблему. Мне кажется, что тут вы фактически эмулируете ООП, теряя все основные приемущества ФП, потому что этот подход дает гибкость и абстрагирование необходимые в любом достаточно сложном коде, ради которых это самое ООП и придумали.
В ФП вместо функции, модифицирующей какое-то внутреннее состояние вы используете чистые функции, которые принимают состояние и возврщают новое состояние. Но это практически не решает никаких проблем, а лишь переносит их в точку вызова.
Проблема ООП не в том, что вам не видно, есть там сайд-эффекты или нет. Если не использовать антипаттерн "миллионы глобальных флагов", то любой не-const метод меняет состояние класса. Проблема в том, что вы не видите как именно это состояние меняется. Никак нельзя описать ограничения вроде "после вызова MethodA с такими-то параметрами, перед вызовом MethodC с такими-то параметрами, надо обязательно вызвать MethodB с такими-то параметрами". Так и в ФП, вы явно видите, что вот эта функция возвращает новый стейт, она его меняет, но как именно - не видно. В ФП, теоретически, вы могли бы описать это через возвращаемые типы, но на практике это невозможно, ибо комбинаторный взрыв и расписывать все внутренние состояния одного только энкодера вы задолбаетесь. И как это в полиморфном коде работает, я вообще не представляю.
Какие там еще основные приемущества ФП? Авто-распараллеливание? Не работает, потому что у вас почти все функции принимают результат работы предыдущих функций. И если вы хотите разные методы одного Encoder параллельно выполнять, то у вас те же самые проблемы, что и в императивных языках.
Это не значит, что ФП хуже ООП, не поймите меня не правильно. Это значит, что хорошо использовать инструменты подходящие под задачу и хороший язык программирования должен давать возможности многих подходов. А еще, приведенная мной задача все еще конр-пример к статье автора, раз вам надо использовать что-то, что крякает, скорее как ООП, а не как ФП, для ее решения.
Во-первых, сама задача не очень богата на бизнес-логику, тестируемую в изоляции от внешнего мира. Условно, писать шелл-скрипты на ФП тоже не то чтобы демонстрирует все его прелести, а тут всё сводится к дёрганью внешних 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
Тем не менее, у вас там тоже есть штука "Encoder" которая описывает набор функций, в точности как интерфейс в C++. У вас есть конкретные его реализации для каждого конкретного кодека, а так же штука, что модифицирует их поведение делая fallback. При чем эти штуки, хоть это и не C++ классы, а замыкания, являются набором кода и данных, с которым он работает. Если вы хотите эти данные как-то поменять снаружи, вы вызываете лишь выставленные наружу функции, а не меняете его прямо, ровно как работает инкапсуляция в классах.
У вас есть функция, возвращающая конкретную реализацию в зависимости от того, какой вам кодек нужен, но в виде общего Encoder в точности как фабричный метод.
Там, где вы вызываете функции encoder у вас некий абстрактный encoder и вы не знаете, что там конкретно за функция вызывается, вы лишь знаете, как она выглядит снаружи, какой у нее тип, что в точности как в ООП вы знаете интерфейс базового класса. В точности как полиморфизм в ООП.
У вас точно такое же логическое деление на каждый конкретный энкодер (функция делающая, например, vp8 software), у вас есть какой-то общий тип функции для всех энкодеров, у вас есть swFallbackEncoder, который держит логику отката и выглядит снаружи ровно как encoder. Вы точно также добавили бы DumpingEncoder для сливания данных в файл, если он нужен.
Вы отказались от class/instance потеряв какую-то часть проверок, ведь теперь вы можете в коде в качестве Encoder использовать и условное замыкание для шифрования пикселей, у которого по случайности и недосмотру главный метод также обозвали Encode(). Если вы добавите их, то вам компилятор еще и проверит, что вы в качестве encoder используете именно то, что вы обозначили, как являющиеся им. И ваше решение в общем-то останется точно таким же.
Но у вас фактически есть иерархия каких-то штук, полностью отражающая иерархию классов в ООП реализации. Ну, называются эти штуки не классы, а замыкания. Общий дизайн системы точно такой же. Это суть ООП подхода.
С зависимыми типами можно выразить что угодно.
Я понимаю, что любое конкретное ограничение для конкретной реализации Еncoder можно относительно легко записать. Но как это работает, когда у вас есть "интерфейс" Encoder? У вас же mkEncoder имеет конкретный тип - он возвращает Encoder. Если вы начинаете вот так вот расписывать возвращаемые типы для условного h264HWEncoder и остальных, это же отражается как-то на описании Encoder? И вот когда вам надо все эти ограничения для всех разных реализаций вместе записать - это очень громоздко и экспоненциально же.
Чтобы ты опять написал "ааа, ыыы, моя не понимать что тут написано"?
Да, действительно такой пример есть. Это UI основанный на оконной модели. Под эту задачу ООП расцвел и стал применяться где надо и где не надо. Могу привести статью, где автор рассказывает где ему пришлось в библиотеке для UI на **функциональном** языке точечно применить ООП подходы.
в современных тенденциях
Эти современные тенденции закончились лет 10-15. Сегодня модно ООП поливать дерьмом, во многом заслуженно.
У ООП много неприятных особенностей, но понимание, что мода на него прошла приходит тогда, когда его ругают те, кто в нем ничего не понимает :)
Думаю статью минусуют за очень агрессивную подачу своего мнения( особенно в контексте джавистов и 99% человечества), мне кажется автору стоит немного пересмотреть эти абзацы и в целом отвлеченные от кода моменты статьи в сторону более нейтральных формулировок. На всякий случай напишу, что тут автор не прав именно по форме, а не по содержанию - там его мнение может быть какое угодно.
А по сути статью - я больше согласен, чем не согласен. Сам когда-то пытался засесть в ООП и после знакомства с плюсами даже чистый Си мне показался лучше, в итоге плюнул и ушёл на Python. Очень большая перегрузка ключевыми словами, необходимый "обвяз" к каждому классу в виде конструктора/деструктора, по две пары методов на каждое поле и негибкость в моментах обработки бьет(сам чаще всего пишут в функциональном подходе, на классах только ORM). Я бы от себя ещё добавил, что инициализация и создание класса как операция чаще всего стоят больше, чем вызов функции, а иногда и по памяти бьют сильнее, в результате чего можно получить хуже работающий код.
Но чтобы прям всё ООП плохо это тоже слишком сильный тейк. Когда-то с коллегами дошёл до вывода, что Java крепко держит средний и большой сегмент разработки за счёт двух вещей: универсальной компиляции на любых устройствах и самодокументируемости интерфейсов(если вести их согласно адекватности и рекомендациям).
Я к минусам отношусь спокойно, и разрешаю себе говорить то, что думаю. Вот в коллективе да, речь фильтрую конечно, софт скилы, все дела.
Тут не больше про минусы, а про сам факт её восприятия - если хотите убедить людей, что ООП плохо, то не стоит это делать в такой форме, когда многие не согласятся с вашим мнением исключительно из-за формы подачи. Можно говорить всё, что думается, но говорить это так, что даже за самые большие грубости в нос не получишь :)
А так из статьи могла выйти хорошая дискуссия и диалог, который смог бы переубедить некоторых людей пересмотреть свой подход к коду. А из-за некоторых моментов часть комментариев направлено не на содержание статьи и диалог, а на агрессию и соответственно ответную агрессию на вас, как на автора.
универсальной компиляции на любых устройствах
А TypeScript запускается не на любых устройствах?
самодокументируемости интерфейсов
Вы удивитесь, когда узнаете про какой нибудь GraphQL на TypeScript, с автогенерацией не только типов модели и сайтом с полной документацией и возможностью запуска запросов, но и кода доступа. Java и рядом не валялась.
Но чтобы прям всё ООП плохо это тоже слишком сильный тейк
Тогда должен быть хоть один пример кода, где ООП выигрывает ФП. Но что то его все нет и нет.
Сам когда-то пытался засесть в ООП и после знакомства с плюсами даже чистый Си мне показался лучше, в итоге плюнул и ушёл на 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# тот же поддерживает и делегаты и карирование функций, и методы не принадлежащие классам, и конвеерные операции. И при этом чтобы запомнить на каком месте находится пользовательский фокус, написать утилиту или сделать оптимистичный лайк - не нужно изворачиваться
Интерфейс Formatter работает только с User. Что значит "Переиспользовать для собаки" - вы угораете? Formatter работатает ТОЛЬКО с User - в этом и заключается контракт. User должен печататься ровно одним способом, как определит бизнес-аналитик (не вам решать). Собака печатается совершенно другим способом (учитывая еще развилки для WebFormatter и RTFFormatter).
Что значит "получается что Formatter зачем то требует целиком тип User со всем что у него есть, используя всего лишь firstName и lastName?"? User - это цельный законченный класс, он передается через указатель любому классу и методу, который его обрабатывает, без потрошения и изменения. Вы каждый раз распаковываете поля и передаете методу, который что-то делает с информацией из User? Зачем?
Самое главное - если потребуется поменять набор полей, добавить, например "Обращение", ("Сэр", "Леди") в User и изменить метод печати, то в моем примере, меняется только классы User, и реализации format (Интерфейсы не меняются), и SomeClass тоже не меняется. А в вашем случае придется менять ВСЕ вызовы в приложении, а их тысячи может быть. То есть, детали реализации User и печати проникают в тысячи других классов всего приложения. Не надо так.
Вообще, я не понимаю, что вы понимаете под словом "Переиспользовать"?
Ты же можешь себе представить хоть один метод хоть в одном классе? Вот как его переиспользовать в другом?
Никак. Именно для этого методы и привязываются к классам. Чтобы их нельзя было использовать с другими структурами данных, на работу с которыми метод не рассчитан. Если вам для какого-то метода это не подходит, значит неправильно, что вы поместили его в этот класс, значит он должен быть где-то в другом классе. ООП не требует, чтобы GetDisplayName() был именно в классе User.
Да. Инстанс DisplayFormatter создается один раз и затем инжектится во все инстансы классов, которые его используют. В DisplayFormatter нельзя добавлять поля, которые используются в бизнес-логике, только инфраструктурную информацию, типа подключения к базе данных, логер и настройки из yml (кстати, как вы будете хранить эту информацию в ваших чистых функциях?)
Самое главное, как вы будете определять, какую функцию передавать в другую функцию в случае, если например, для одних настроек приложения надо использовать DisplayFormatter, а для других - RTFFormatter? В Spring и вообще при применении DI фреймворк создает нужный инстанс и инжектит его в классы потребителей (в SomeClass) - в поле с интерфейсом Formatter присваивает нужный инстанс.
Будет забавно, если кто-то напишет ответную статью, где перепишет все куски кода правильно в ООП. Можно будет тогда хоть целую цепочку статей сделать, где внешне взятый плохой код будут последовательно переписывать на две разные парадигмы и сравнивать.
Что вы делаете в публичных обсуждениях, если вы такой ранимый и смотрите на подачу, а не на содержание. Как по мне, нормально написано, хоть и не со всем согласен.
Это не ранимость, а самоуважение.
При чем тут я вообще? Я говорил о форме в контексте людей,которых уколы могут задеть(я джавистом не являюсь, но ставить на всех одно клеймо это глупо).
Если вам нормально носить штаны на голове,то совсем не обязательно,что всем остальным будет тоже удобно. Полистать вниз - увидите достаточно комментариев людей,которых статья оттолкнула ИСКЛЮЧИТЕЛЬНО из-за формы написания.
Есть стили подачи текста и не стоит перебарщивать с агрессией, если хотите донести мысль, а не довести читателя.
Тут ведь как: если человек задаёт вопрос кому-то и ему принципиально важно его мнение - он может приложить усилие и отфильтровать суть от подачи.
Тут же обратная ситуация: человека никто не спрашивал, он сам вышел на публику донести какую-то мысль до окружающих. Если в этом его цель - то уже в его же интересах написать эту мысль на языке этой аудитории, уважительно, не оскорблять её и т.п. Иначе внимание будет оттянуто от формы к содержанию, и цель будет провалена.
Так что или автор не справился со своей целью из-за выбранной подачи, либо цели "донести мнение" там и не стояло. Если ему просто хотелось назвать широкий круг лиц "плохими инженерами" и начать холивар - тут полный успех, не отнять)
по сути верные тезисы про удобство ФП (хотя про недостатки не забываем)
но своим экстремизмом автор создал чрезвычайно токсичную, трудозатратную и бесполезную дискуссию
то есть потратил кучу времени на чтение статьи и комментариев и результат ноль
за что ему минус
Я считаю всех кто называет опытных разработчиков сменивших кучу ЯП не профессионалами или не крутыми разработчиками ЧСВшником, который через унижения пытается доказать свою точку зрения.
Для меня это чем-то напоминает западную повестку.
Не утвержаю, что я прав, это просто мое мнение, ведь у каждого человека есть своё мнение. И унижать людей за то, что у них есть своё мнение в сфере, которой они посвятили жизнь, а порой и уничтожив личную жизнь, как по мне очень очень плохие решение.
И как вы без классов и полиморфизма предлагаете реализовывать, например, поддержку объектных файлов - когда есть сущность со специфичными для неё задачами (пробежаться по секциям, посмотреть архитектуру и т.п.), для которых частично применимы какие то общие алгоритмы, но для серьёзной работы нужен отдельный код (скажем, для поддержки ELF на Linux, PE на Windows и т.д.)?
Это понятно, но чем поможет отказ от классов и переход к отдельным функциям? Идея о том, что не надо без необходимости менять глобальное состояние, вполне реализуется и в ООП.
Ну т.е. мы сначала делаем в ФП полиморфизм, инкапсуляцию данных и переиспользование поведения через интерфейсы и композицию, а потом говорим что ООП не нужен.
Поддержу автора.
Есть пример, это как в gamedev приходят к архитектурному паттерну ECS. Паттерн помогает избежать кучи гемора, связанного с ООП. Больше не нужно конструировать сложные классы и придумывать как они должны взаимодействовать. Данные отделены от представления, легко писать логику, легко проверять. И главное работает быстро, из-за TypedArray.
Видимо слишком революционные идеи :D
ECS это просто паттерн, который прекрасно сочетается с ООП, тут нет противопоставления. Это как заявить что MVC или MVVM это замена ООП
По-моему он довольно плохо сочетается. ECS предлагает данные хранить в виде компонентов без логики и без приватных полей, а поведение - отдельно от данных, в системах без стейта. Так что с точки зрения пользователей он намного ближе к ФП чем к ООП.
DTO и хэндлеры. Так можно сказать что CQRS с ООП не сочетается.
Ппц, опять безумцы воюют с инструментами, когда надо бы воевать с неадекватным использованием инструментов 🤦🏻♂️

За 13 лет автор ни ООП не освоил (привет "истинному" полиморфизму с копипастой свичей), ни ФП (привет замыканиям с изменяемым состояниям).
И я даже сделал секцию Частые возражения, где именно это возражение прокомментировал.
Функциональное программирование - это не про создание функций. В ядре линукса не функциональный подход, а процедурный, с частичками ооп. Если брать чисто функциональные языки, наверное это лисп.. больше вспомнить не могу
Вы тут сами запутались. Для ФП принципиально важно, чтобы были first-class functions и HOF, без этого действительно получится ПП
Обычно, когда видишь такие статьи, машинально прокручиваешь до комментариев в надежде, что вот там-то умные люди напихают полную панамку автору, который даже в терминологии не разобрался.
И что там? Половина комментаторов вслед за ним называет процедурное программирование функциональным. Вторая - комментирует так, как будто его статья реально про ФП ("математический стиль" в альтернативно-одаренной терминологии автора).
Спасибо вам за этот комментарий! Я до него доскроллил, и моя вера в здешнюю публику не умерла окончательно.
Повторяю, я ваше мнение про "это процедурное" даже отдельно разобрал в статье.
Альтернативно-одаренным нужно внимательнее читать.
А так да, это моя терминология исходя из реалий, что вы сами признали - большинство использует понятие ФП именно в этом контексте, и бесполезно с этим спорить. Дальше будет только хуже - слово процедура никто и не вспомнит.
А математический стиль - куда более правильное название как по мне, потому что понятие "функция" там используется из математики.
Самый популярная библиотека самых популярных языков - React, давно перешла на ФП. В Linux - ФП. Основа говнокода разве что, но про это и статья, и обложка статьи.
В Linux - ФП.
Разрабам Линукса об этом сообщите, а то они не в курсе и какие-то странные вещи пишут в статьях: https://lwn.net/Articles/444910/
И хватит уже называть процедурное программирование функциональным, это кринж.
Да-да, те самые легендарные чистые хуки из реакта, которые нельзя использовать в ветвлении, иначе всё сломается.
Они, как правило, написаны на Фортране и Коболе.
А по-моему, здорово.
Как вообще можно делать глобальные выводы о языке по конкретному файлу исходного кода?
А если кто-то гениальный (неиронично) напишет идеальный код на JS, который посадит космический корабль на Марс, то всё, теперь все обязаны будут признать, что JS идеален?
Реально было бы желание с этим разбираться?
Да. Это же космический проект, где код вылизывают для максимальной эффективности, а не очередной жирный каличный веб-сервис. От последних меня тошнит, а первое даёт волю внутреннему перфекционисту. Ну и цель воодушевляет тоже.
очередной жирный каличный веб-сервис
Веб-сервисы потому и, как вы выразились, "каличные", потому что ответственность, возложенная на них, гораздо ниже, чем у "космического кода". Никто не будет вылизывать код веб-сервиса до идеала, на это попросту никто ни времени, ни ресурсов выделять не станет.
Фортран-то к тому времени уже сменил 4 версии и вошёл в пору своего расцвета, но бортовые компьютеры его не тянули.
Небезызвестный американский брокер Bloomberg, например, использовал систему из 25 миллионов строк на Фортране. Вроде, до сих пор пытаются переписать.
Про программы каких-нибудь ядерщиков никто нам не расскажет, но думаю, что там бывает больше.
Вообще-то современный Фортран имеет одну из лучших модульных систем среди всех языков программирования (и уж точно намного лучше C++), поэтому хорошо приспособлен для написания больших программ.
Я заинтересован узнать о об этом больше (про модульную систему Фортрана)
Там полноценные модули в стиле Ады, с полным дискретным контролем над экспортом и импортом, с инкреметальной раздельной компиляцией, позволяющей в том числе компилятору совместно оптимизировать разные модули. При этом в последних стандартах своеобразный параметрический полиморфизм даже есть, без всяких извращений вроде темплейтов.
Причём в одной и той же программе можно использовать и модули в новом, и в старом стиле, то есть просто раздельно транслируемые объектные файлы, как в Си. Так что модуль, написанный в 1964 году, нет никаких проблем подключить.
Да вопрос вообще в другом как я понял из статьи. А нужно ли ООП в 99% современного ПО если там спокойно можно обойтись ФП (в терминологии автора, я то понимаю что это ПП по академическому)? И возможно популярность ООП результат маркетинга и массового обучения ему начиная со школы? Ну вот как сейчас помню эту ахинею про то что "ооп оказался революционным подходом в создании программ так как является отражением реального мира". "Чиво блять?"
Я вам больше скажу. В современных практиках максимально отходят от наследования и заменяют их композицией. 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 нужен?
А также ObjectPascal и последовавший за ним Delphi. А вдохновителем - Simula и SmalTalk.
"После этого" не равно "вследствие этого". Просто совпало, что ООП начали широко рекламировать, учить в вузах (как сейчас рекламируют и продвигают раст)- соответственно появилась критическая масса ооп погромистов пишущих ооп программы. Но из этого не следует, что весь этот пласт ПО нельзя было бы (или не был бы) написать на ФЯП.
Исторически ФЯП существовал даже до ООП, но фактически именно ООП обеспечил качественный рывок, плюс существующий классический процедурный подход, без него никуда. Даже более редкий и экзотический Форт и то оставил более заметный след в истории, чем ФП.
Можно. Но зачем?
ФП(настоящий, а фантазии автора поста) гораздо сложнее с точки зрения концепций и написания кода. ООП позволил массового переиспользовать код, выпуская огромные фреймворки для всего на свете, тем самым сделав программирование дешевле для индустрии, что и привело к взрывному росту.
У меня одного сложилось впечатление что автор пытается пере изобрести 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% тупые и не понимают что пишут.
Мне вот интересно, а те кто выезжает на встречку тоже ведь наверно думают что вокруг все идиоты и едут не в ту сторону.
Профессиональной среде рукожопов, выдумавших 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 всё же повыше порог входа с его метапрограммированием.
Не сходится, 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К строк.
Методы не проигрывают функциям, потому что они делают одно и то же. ЕДинственное отличие - они принадлежат конкретным объектам. Если этот метод у тебя применим к разным ситуациям (разным объектам) - он не должен быть методом конкретного класса, а быть, например в отдельном классе или просто быть функцией вне классов.
Наследование - смысл наследования в создании общих данных и методов между разными классами.
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*
Плавание я могу реализовать один раз и использовать в разных классах. Например в 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 не выведет ничего кроме примитивных типов
А я вам как раз и предлагаю переопределить формат вывода для примитивного типа.
Массив - не примитивный тип. Он не выводится обычной командой Writeln
Ну хотя бы значения типа boolean попробуйте заменить на "истина" и "ложь".
Это все равно будет отдельная функция для ввода. Никто не пользуется напрямую функцией writeln внутри своего кода. Используется либо класс для вывода данных, либо просто свой метод вывода. В котором, я могу как угодно вывести любой тип без переопределения чего-либо.
Так делается потому что:
Не все типы можно вывести
Контекста вывода в консоль может не быть
Требуется часто вывод в более специфическом формате
Можно изменить контекст вывода (например, если это, лог в файл)
Да дело-то не именно в выводе. А в том, что вы в парадигме ООП вообще не можете изменить поведение кода, который не сами написали. Можете только отнаследоваться от него в свой код и там менять, либо править чужие исходники (из чего возник open source).
Можем и постоянно меняем. Повсеместно меняем поведение неподконтрольных нам классов. В той мере, в которой это позволено. А если не позволено - наследуемся и меняем. Это одна из базовых вещей в ООП - возможность запретить что-то менять в классе.
Я вам даю честное слово, что вы не можете в Паскале перевести false и true на русский язык не потому, что западные империалисты специально хотели это вам запретить.
Нет в Паскале такого понятия вообще. Вывод всегда на совести разработчика. Стандартный вывод - это для начинающих. И вывод значения boolean нельзя вывести через writeln как хочется, поэтому что выводятся сырые данные из памяти. У примитивных типов нет никаких методов ВООБЩЕ. Нет никакого ToString. Нет методов! Выводится данные из памяти. Всегда выводятся данные через свои методы вывода данных. И вариант вывода очевидно полностью под управлением пользователя.
И вывод значения boolean нельзя вывести через writeln как хочется, поэтому что выводятся сырые данные из памяти
Вы думаете, что в памяти находится слово "true"?
У примитивных типов нет никаких методов ВООБЩЕ.
Это проблема даже не ООП в целом, а конкретно языков вроде Паскаля. Просто семантический косяк, вызванный неэффективностью обращения к методам через VMT.
В памяти находится 0 или 1 (или -1 и 0). Boolean - это 1 байт (именно байт, в Паскале).
Паскаль - компилируемый язык. Примитивные типы там - НЕ КЛАССЫ и ОБЪЕКТЫ, а просто ссылки на данные в памяти. У них нет никакой VMT и быть вообще не может.
Ваша проблема, которую вы решали в вашем посте, в Паскале вообще существовать не может. Её там не будет. Так что это "семантический косяк" вашего языка, а не Паскаля
Плюс ко всему, Паскаль не заложник ООП и прекрасно может работать без него, когда это действительно нужно.
Существовать не может, потому что примитивные типы не имеют методов в принципе и следовательно их не надо переопределять.
Это решается тем, что изменяется метод вывода конкретного типа. Этот метод всегда пользовательский. Не надо ничего городить и оверхедить. Тут нет никакого вообще наследования. И ООП тут вообще никаким боком
ООП в популярном понимании основывается на иерархии классов. Это такой средневековый подход, когда всё в мире считается иерархичным. Не очень жизненно
А разве множественное наследование не лучше?
Множественное наследование даёт очевидную проблему с ромбом, но при этом всё равно не разрешает вопрос по существу, потому что даже и с альтернативными путями иногда невозможно установить иерархию в строгом смысле, т.е. без циклов.
Множественное наследование даёт очевидную проблему с ромбом
Решенная проблема (как правильно рядом указали) проблемой уже не является.
но при этом всё равно не разрешает вопрос по существу, потому что даже и с альтернативными путями иногда невозможно установить иерархию в строгом смысле, т.е. без циклов
Что вы имеете в виду?
Я имею в виду, что не всё в природе иерархично. Поэтому в принципе два класса могли бы взаимно наследоваться, но модель наследования обычного ООП этого не позволяет. Это как сопрограммы по отношению к подпрограммам.
В практическом плане, например, у меня в программе среди прочих есть два модуля, макросы и сеть. Это как бы денотационный и операционный семантические процессоры. Макросы используют сеть, чтобы взаимодействовать, а сеть использует макросы, чтобы обрабатывать пакеты. То есть они денотационно объединены циклической логической связью, а операционно - взаимной рекурсией. Боюсь даже представить, как тяжело было бы реализовать такую архитектуру в вульгарном ООП. Пришлось бы предусматривать какие-нибудь коллбеки с "нижнего" уровня на "верхний" и тому подобную дичь. Или накидать всё в один суперкласс. Хотя в смоллтоковском ООП, (не использующем наследования) конечно, никакой проблемы бы не было. Ну и функционально, разумеется, проблемы нет.
Не обязательно, но все объекты, к которым применяется одна функция, должны быть подтипом объекта, который работает с этой функцией. Иными словами иметь обший интерфейс, относящийся к ней.
И это чертовски правильно, потому что применять функцию на данных, ей не подходящих - это бессмысленно. Garbage In - Garbage Out. А в контексте разработки ПО - даже опасно, потому как может вызвать ошибки.
И если ваш язык такое позволяет - это не очень хороший язык. Ведь другие защищают вас от случайных ошибок этого класса.
Вопрос тут в том, кто определяет, какие данные подходят функции.
Разработчик?
Ну технически функция очевидно должна определять - только она знает про алгоритм, соответственно только она знает про подходящие типы и инварианты.
Может показаться, что некоторые явные ограничения избыточны.
Например, зачем использовать интерфейсы и типы, если функцию можно применить на любом списке или любом объекте с полем Х.
Но на самом деле не все такие операции имеют смысл.
Например операция "Задай имя и фамилию", меняет лишь поле "Имя" структуры "пользователь" и теоретически может работать с любым объектом, имеющим имя. Но проблема в том, что логически эта операция имеет смысл только с пользователям и попытка применить его на "заказ" (тоже имеющий имя) - бессмысленная и ошибочная операция. Позволять выполнить эту функцию с объектом "заказ" значит позволить совершить случайную ошибку, которую можно избежать.
Очевидно, что функция "задать ФИО" не должна применяться к заказу. Но функция "отсортировать по именам" или "перевести имя на китайский язык" вполне может применяться и к людям, и к заказам. Всё зависит тут от самой функции.
Именно так.
Но это значит, что разработчик, создающий функцию, должен быть способен задать ограничение типа по своему желанию. "Только для заказов" либо "любое поле, поддерживающее сортировку"
Я именно это и утверждаю. Но это параметрический полиморфизм.
как переиспользовать из вашего класса GetDisplayName для собаки, наследуемой от Animal?
Никак. У пользователя 2 поля firstName и lastName, у собаки 1 поле name или nickName.
У меня вот отчество есть.
Вообще, представление имени человека в виде полей firstName и lastName – это один из самых вредоносных антипаттернов, если задуматься.
И я замечу, что вы не ответили на заданный выше вопрос. Как получить GetDisplayName для человека и собаки одновременно? Например, в билете на поезд.
Вообще, представление имени человека в виде полей firstName и lastName – это один из самых вредоносных антипаттернов, если задуматься.
Почему? Если у вас официальная бумажная форма, в которой есть отдельные поля "Фамилия, Имя, Отчество (если есть)" или интеграция с другой системой, например для покупки билетов, где поля "Имя" и "Фамилия" отдельными полями. Какие у вас варианты, кроме как и в вашей системе хранить все эти поля именно так, по отдельности?
Варианты? Разметка.
Потому что у американца эти поля в официальной бумажной форме другие, а у китайца третьи.
Это почти такая же дичь, как кодировка ASCII.
А в чём практический смысл выбрать всех Ивановых?
А если ещё задуматься, что "всех Ивановых" подразумевает и "Иванов" и "Иванова", но второе – только для женщин...
А что вы имеет ввиду по "разметкой"?
Это почти такая же дичь, как кодировка 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. И много по мелочи.
Забыл еще момент - глубокое копирование почему то идет через костыль с сериализацей и десериализацей в 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) Копипаст один из них, и да, это не всегда плохо, все зависит от конкретных целей и задач. Где-то лучше копи паст плюс добавленная/немного измененная логика, чем универсальная функция с кучей 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. А то весь ваш посыл выглядит неконгруентно, как "пчёлы против мёда".
Автор ChatGPT спорит сама с собой
"Заставь дурака ООП следовать, он 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 например, и ФП-реализация очевидно проиграет по производительности.
Вы бы хоть уточняли, куда завезли и как там у них с мутабельностью?
С теорией то всё понятно. В теории код не надо на реальном железе выполнять.
старым именем переменной после мутации пользоваться нельзя
т.е. всё-таки мы получаем ссылку на другую область памяти, а не изменяем данные 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)Ну вы примерно и написали, с точностью до синтаксиса, что характерно. Только в последней строке используется arr`, а не arr. Массив "съеден" изменяющей его функцией change_nth_value.
Подозрительно это всё. Получается, на уровне кода не показать in-place мутабельность. А есть какие-нибудь бенчмарки сравнения с какой-нибудь Java? Обычно, в бенчмарках сплошь и рядом алгоритмы, опирающиеся на работу с массивами.
Вы просто не понимаете язык. Если бы вы не знали, что означает Сшный знак =, вы бы тоже говорили, что «на уровне кода не показать in-place мутабельность».
По спецификации языка функция update изменяет ячейку физического массива, убивая привязку старого имени и создавая новое имя
https://cloogle.org/src/#base-stdenv/_SystemArray;icl;line=37
Если хотите проверить гарантии mutable update — ну флаг вам в руки.
Можно небольшой пример? Создаём массив со значениями от 1 до 5, меняем его первый и последний элемент местами, выводим весь массив на экран.
Автор топит не за функциональный (= математический стиль), а за ФП. А в нем можно работать как в мутабельном, так и в иммутабельном стиле.
А в чём вы видите проблема с массивами и строками?
Язык 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". Это надо делать на уровне представления в какой-нибудь вьюМодели, ну или сделать отдельный форматтер, если надо реюзать один формат в куче мест.
Не совсем понял. В начале вы говорите так, что подразумевается какая-то неоднозначность справедливости позиции автора статьи. Но дальше я не вижу никакого противопоставления. Вы просто говорите, что данные окна удобно хранить в классе, но про методы ничего не сказали. Они тоже у окна удобнее, или это отдельные функции ?
после этого примера перестал читать, сразу видно "фпшника"

Вы всё с технической точки зрения смотрите. Но по факту ООП важен просто как способ описать интерфейс зон ответственности между разработчиками в команде. Ты пилишь класс, отдаёшь API и контракт другому, и он не напрягает мозг что там внутри.
ФП хорош для некоторых задач типа парсинга (оптимальнее или гибче), но когда всё чисто ФП -- читать и понимать код становится сложно. В больших проектах на функциональных языках тоже зачастую торчит ООП вокруг слоя ФП.
Не знаешь, зачем надо - не используй. Так-то можно и цикл через goto организовать.
Все так как вы описали. Есть даже известная в узких кругах шутка про то во что можно превратить простой http абстрагируя его в классы java HttpServletRequest класс. Если еще не пробовали, то посмотрите на язык clojure. Автор этого языка еще написал статью-книжку к издающемуся иногда альманаху про языки программирования зачем он его вообще создавал и какие проблемы пытался решить. Во многом это перекликается с вашей статьей.
Если ФП и ПП это одно и то же (что довольно спорно), то получается ФП появилось раньше ООП. И раз ООП всё-таки придумали - значит, были в ФП какие-то проблемы, которые требовали новых подходов. То, что вы этих проблем не видите - ну может быть это в вас дело, а не в отсутствии проблем
ФП в любом случае появилось раньше ООП.
значит, были в ФП какие-то проблемы, которые требовали новых подходов
Overqualification. Трудно, дорого и качественно, а рынку нужно по рабоче-крестьянски, дёшево и тяп-ляп.
Фп труднее ооп? Но ведь статья как раз про трудности с ооп в том числе. В чем сложность с фп? Сущностей меньше
Кстати в реакте ушли от классов потому что классы сложнее для джунов (лол), это в офф документации написано а не по иным причинам
Вообще зачем выбирать что то одно, я использую и фп и ооп одновременно где что лучше подходит а не нятягиваю сову на глобус
раньше были другие носители данных, ленточные это подразумевает последовательность скорее всего это односвязный список, данные имели разбитый характер на много маленьких кусочков, таже перфокарта с засечками
следующая тема метка, пометить тоесть засечка/разметка
ФП обычно идёт неразрывно с неизменяемыми структурами данных. Это уменьшало производительность программ, актуальных в 80-90 годах. Сейчас уже разница в производительности не особо велика в подавляющем большинстве случаев, т.к. компиляторы уже научились многое оптимизировать. Зато ФП сильно повышает надёжность программ.
Почитайте секцию определений - ФП у меня это не математический стиль ФП.
Вы лучше сами почитайте определение ФП: https://en.wikipedia.org/wiki/Functional_programming
Нет никакого отдельного "математического стиля", не выдумывайте.
Меня не устраивает общепринятое определение, и я выдумал более логичное.
Вы просто взяли определение процедурного программирования xD
А функциональное обозвали математическим стилем. Это тупая подмена понятий.
Я добавил определение процедурного стиля, и оно совсем отличается от моего Фп. Что в нем не так?
В примерах я возвращаю из функций новые функции с замыканием - это ПП?
Я добавил определение процедурного стиля, и оно совсем отличается от моего Фп. Что в нем не так?
ПП - это не стиль ФП, это совсем разные парадигмы. Так что в ваших определениях всё не так. Почитайте хотя бы Википедию, если книги по 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, мы не можем понять, что он может: какие у него бизнес-правила, модель тупо плоская. Все размазано по всему проекту, нет четкого отделения предметной области, такое очень тяжко поддерживать.
Я ни в коем случае не считаю фп плохим. Я сам очень много писал на реакте и рад, что застал эру именно функциональных компонентов.
Оскорбления - отдельная тема, статью будто Стас ай как просто писал, уровень дискуссии в Восточной Европе как всегда высок.
К сожалению, программирование достаточно далеко от науки (как и я), поэтому многие термины могут быть интерпретированы по разному, так что давайте сначала с ними определимся.
Мне кажется, за последние 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;
}Например мой мозг любую IT систему представляет сразу как набор классов, свойств и методов. А вот процедурным стилем вообще не представляет.
Примеры кода, которые так хочет автор.
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"}
Дальше неохота разбирать, коммент и так уже большой.
писать на плюсах православно, с шаблонами и без классов
Тогда в большинстве случаев придётся использовать указатели. Но что, все же, большее из зол?
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 в разных компаниях, на котором мало кто хочет писать и для рекрутеров это большая головная боль, зато себе в карме человек поставил галочку - протащил свой любимый яп.
Мораль моего комментария - такие статьи очень опасны (особенно на хабре), т.к. молодые джуны и мидлы после прочтения уверуют эту идею, и будут потом всем доказывать, что ООП это зло, а ФП это круто и удобно.
сегодня, с утра по раньше, я заварил чашечку кофе и решил почитать хабр, как в старые добрые времена, и наткнулся на эту статью. как я понял в ней обсуждается, что спагетти код лучше ооп парадигмы, что в целом ставит под сомнение экспертность автора в программировании. кто бывал на подобных проектах, привет кодекс, знает что лапшу поддерживать сложно и не безопасно для здоровья, но в целом ваша незаменимость в такой игре выше, а так как заменить вас становится сложнее чем классического разработчика, и зарплату поднимать будут чаще.
Такое ощущение что статью писал человек которому так и не удалось перешагнуть порог "hello world" и вместо того чтобы работать над собой, автор решил убедить, что все вокруг не правы, а он с его "видением мира" молодец, собственно количество минусов у статьи подтверждает мои слова.
Мир не бывает черным или белым, он состоит из оттенков, также как и задачи решаемые в каждом конкретном случае. ФП и ООП это всего лишь инструменты, каждый из которых хорош в своей ситуации.
ФП - это как правило меньшая гибкость, нарушение многих принципов SOLID, сложности в поддержке и масштабировании ПО. Со временем это превращается в лютое легаси (https://ru.wikipedia.org/wiki/Большой_комок_грязи ) которое в итоге переписывается на ООП решение со всеми его прелестями (инверсия зависимостей, принцип единой ответственности, наследование, гибкость в случае расширения, применение DDD, более высокая скорость разработки на длинной дистанции и тд)
В 90% задач перед разработчиком стоит классическая задача реализовать некое ПО которое будет заниматься операциями ввода / вывода, будет легко поддерживаемым и масштабируемым, но никак не требуется "выжать все" и достичь фантастической производительности, а это значит ФП точно не вариант.
Именно поэтому ФП имеет смысл лишь в ПО
где нужно достичь максимальной производительности любой ценой
слишком сырое решение, например наколеночный "Proof of concept"
нарушение многих принципов SOLID
Это уже является самостоятельным составом преступления для суда инквизиции?
Именно поэтому ФП имеет смысл лишь в ПО
Не только лишь в этом. В принципе оно довольно удобно в тех местах, где надо преимущественно считать, например для реализации каких-нибудь фильтров и прочей обработки сигналов. Поэтому например Wolfram или R хоть и поддерживают разные парадигмы программирования, в основном ориентированы на функциональное.
Слушайте, я вас понял.
ООП (и я с этим согласен) имеет массу недостатков и косяков. Spring, Дядя Боб и SOLID делают все возможное, чтобы решить проблемы ООП.
И я, как и вы, считаю, что надо возвращаться обратно к процедурному программированию (хотя вы называете это функциональным, но вам уже указали, что вы на самом деле жаждете процедурного).
Конечно, в язык с ПП надо добавить возможности, которые ООП дает, в частности, Spring - бины или ссылки на модули, полиморфизм модулей, DI. Таким образом, чтобы язык процедурного программирования в конечном итоге реализовывал принципы SOLID.
отказывайтесь от языков, где классы это основа (Java, C#, C++ и др.). Пишите качественный — максимально простой, функциональный код.
Года полтора-два назад, с появлением LLM и сопутствующего им хайпа, на хабре практически перестали появляться новые статьи на тему все должны писать на хаскеле.
Я предположил, что поклонники хаскеля массово обратились в новую секту - AI, GPT и вот это все.
И тут на тебе. Понимаю так что ИИ-хайп действительно перешел в режим спада, и сектанты обратно к хаскелю потянулись ... ))
Вас, как и многих других комментаторов, автор ввёл в заблуждение (не знаю, намеренно или по собственной искренней неадекватности) - он функциональным программированием называет процедурное, потому что там функции 😂
Называть функциональным программированием программирование функциями это очень смешно.
Функциональное программирование называется функциональным не потому что там используется что-то с названием "функции", а потому что функции там математические, а не императивные. Могли бы хотя бы Википедию прочитать, прежде чем спорить.
Ты бы мою статью почитал сначала, секцию определения.
Нафига они мне?) У меня уже есть общепринятые определения.
Если бы вы просто обозначили какие-то понятия каким-то своим названием, то это нормально, хотя и странно. Но вы пытаетесь доказать, что ваше название правильное, что есть объективные причины использовать такой термин. А это не так. В общепринятом понимании этот термин означает другое, и я вам указал, что причины его появления не те, которые вы представляете.
Эта проблема носит даже отдельное название — Проблема банана и макаки от Джо Армстронга: тебе нужен был банан, но ты получил макаку, держащую банан, со всеми джунглями в придачу.
Если у вас приложение спроектировано так, что банан наследуется от макаки, или макака от банана, или макаке нужно "отрезать руку" и пришить кому-то другому, то это проблема вашего конкретного приложения, а не ООП в целом.
А если вы считаете, что ООП в Java/C# - это Сложна, то вы еще не видели какой-нибудь Swift, где можно в несколько простых шагов насочинять свой собственный синтаксис, а развитие языка достигло такого маразма, что в community пытаются пропихнуть идею запретить объявлять переменные без приписки контекста (фактически к какому потоку будут относиться эти переменные).

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