Pull to refresh

Comments 1251

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

Rust — хороший язык. Но проблемы есть.


  1. Это всё равно выкидывание опыта.
  2. Как-то так сложилось, что раста вокруг меня нет. Вакансии в смежных областях, где можно пользоваться опытом-связями — там скорее хотят видеть плюсы.
  3. Если уж таки переходить, выкидывая опыт и игнорируя эти самые связи, то зачем ограничивать себя растом? Есть и более интересные и приятные языки.

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

Ну я прошел через этот путь, но не так уж это выкидывание страшно, это все равно что избавится от legacy, это на самом деле крутое ощущение, ты будто бы с нуля все делаешь и учишься.
Я вот в конце концов тоже из родного окружения C++/Qt ушел в неизвестность blockchain разработки. А сейчас еще и просто стало много вакансий в области системной разработки на расте, причем именно в штатах. В России то пока немного тухляк, увы.

ушел в неизвестность blockchain разработки

Зря я от одних идрисистов-блокчейнеров отказался с полгода назад, похоже.

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

И да, эти эмоции полезны — в плане изобретения нового и перехода на него.

Я такого мужика в метро видел. Шел он такой, из последних сил тащил какой-то баул, и тут бах у него ручка отрывается, баул падает. Мужик сначала не понимает, потом не верит, потом краснеет, кричит на него "НУ И НЕСИ СЕБЯ САМ!!!", пинает его, разворачивается и уходит.

О!
Нас уже трое!
Вы мне не братья? (как в индийском кино) :)

И да, кресты (плюсы) я в институте «ниасилил» (1993-й год), остались всякие паскали/модулы/обероны/чуть асма(совсем чуть)/и всё остальное…

Последние 27 лет я либо мелкий-средний начальник, либо сисадмин, либо и то и то в одном флаконе. ББС, ФИДО, Релком,…

И да: если у вас, ноль-икс-чего-то-там (кажется, какой-то хардварный баг в пентиуме?) 17 — это треть жизни, то калькулятор утверждает, что мы с вами ровесники с огромной точностью… :)

У меня сбылась мечта — я полтора месяца работаю из дома. А т.к. у меня всё отстроено хорошо и очень хорошо, то к компу я подхожу редко и очень редко! Ибо работа — как у пожарного, в «негативе» (все работают, я готовлюсь к инцидентам, если я работаю, то кто-то или даже вся контора отдыхает и ждёт, когда я починю), и мне всегда это нравилось.

Так вот, в самоизоляции я с огромным удовольствием отхожу от компа, отдыхаю от него совсем. Я даже соцсети свои почти забросил.
А до того, лет 10 шутил, что как в Матрице: вынимаю из затылка разъём от рабочего компа, еду домой — вставляю домашний разъём… :) А последние года три 4G и мобила обеспечивали «разъём в дороге»… :)

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

Спасибо за пару лишних поводов для размышлений, наблюдений, сопоставлений и рефлексии! :)
Возможно глупость спрошу, не судите — а Scala? Или Java-мир совсем не интересен?

Ох, Scala лично для меня лежит где-то в uncanny valley на смешении функциональщины и императивщины. Под JVM я бы скорее на Eta писал.

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

Там же не завтипы, а path-dependent types, разве нет? Пародий на завтипы у меня и в хаскеле достаточно.


Ну и у меня, если честно, от её[_] синтаксиса => [вытекают] глаза.

Про Haskell не могу сказать, но Scala одно время сам хотел освоить (в связке с Hadoop и Spark) и глаза не вытекали вроде. Вот R — действительно необычен синтаксисом. Хотя, конечно, все это вкусовщина)))

Ну там можно выразить зависимую пару и зависимую функцию, значит завтипы есть :dunno:.


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

Ну там можно выразить зависимую пару и зависимую функцию,

Ни то ни другое нельзя.

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

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

Можно конкретный пример на скале

Нельзя, конечно. Чтобы построить такой пример, мне нужна конкретная формализованная кодировка, которая позволяет любой тип идриса преобразовать в тип на скале. Когда у меня эта кодировка есть — я тогда могу ее взять и показать, например, что это кодировка не покрывает все типы или покрывает некорректно (т.е. то что получается в результате не ведет себя как завтип). А такой кодировки никто не дал.
До того как такая кодировка предоставлена вопрос в принципе бессодержателен — зав. типов в скале нет, ну просто потому что не существует причин считать, что они там есть. На данный момент просто нечего обсуждать. А если ты попробуешь сделать такую кодировку и доказать ее корректность, то отсутствие завтипов в скале тебе станет полностью очевидно, если ты конечно не полный дебил и имеешь представление о предмете в целом.


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

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

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


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

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

Ну и я не очень понимаю, как оно тут поможет

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


Хинт: на месте типового аргумента в случае зависимых типов может вставать любой терм (или, как минимум, это должно быть какое-то "интересное" подмножество — тогда конечно все равно полноценных зависимых типов не будет, но будет "интересный" фрагмент). Это значит, что если твоя "зависимость" выражается "path'eм", то этот "path" должен уметь кодировать любой терм скалы (либо "интересное" подмножество термов). А оно чо кодирует? Нихера не кодирует.
Это опуская другие проблемы.


Вообще, отличить зав. типы от их отсутствия просто. Вот ты можешь без зависимых типов написать Vec[N], ты можешь даже написать Vec[N] таской, что N нее будет известно в компилтайме. И потом для этого Vec[N] ты сможешь даже написать vecRef(vector, n) — и ты даже сможешь статически типизировать этот vecRef и тайпсейфно его применять в том случае, если у тебя известны n и длина vec, либо только длина vec, либо только длина n. Но вот чекнуть его в случае если неизвестно ни то ни другое — уже не выйдет, ты не сможешь написать refl.

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


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

Мне кажется необходимость поднимать/опускать термы на уровень типов руками не делает язык завтиповым или нет

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


Если ты покажешь пример на идрисе который по-твоему невозможно выразить на скале

Напиши любой ПИ-тип, который можно использовать как ПИ-тип.

Как насчёт read_vec из гайда по индрису?

Эм, а при чем тут типы? Это метапрограммирование. Естественно, на макросах зав. типы делаются без проблем — ну просто можно в компайлтайме эмитить код на идрисе и его тайпчекать.
А типов-то там и нет. И сам код — не типобезопасный. Корректность обращения к массиву там не гарантирована.


И пи-типа ты тоже не показал ArrayFixed[A, N <: Int] на место N нельзя засунуть связанную переменную. Там останется просто Int. И тогда ты сможешь типобезопасно либо получить значение из массива с известной длиной, но с неизвестным индексом, либо с известным индексом — но с неизвестной длиной. В случае если и длина и индекс неизвестна — облом.

Ох, я не просил прям код, просто общую идею :) Но раз так, то я почитал и не понятно flatMap(_.get(2)) зачем? Почем не просто get(2)?

Потому что вы создаете массив в рантайме, он может не создаться (для отрицательной длины в частности), поэтому функция парсинга массива initDynamic возвращает Option[FixedArray[..]], ну а дальше через флатмап комбиинруем две функции возвращающие Option.

А тьфу тупой вопрос был, чо то сбило с толку чтобы признать эту функцию. Я подумал в начале что это map по всему массиву.


А можно достать факт доказательства из get и носить его с собой пока он не понадобится опять?

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

Мне не понятно какой тип у ArrayFixed.initDynamic(...), это каждый раз новый тип? Когда там в дженерик аргументах написано i.type, это как понимать?

Ну если вывести сам массив на печать то получим:


Some(ArrayFixed(5,[I@3fa8e43a))

Чтобы работать с ним тайпсейфно полагаю что его нужно проверить дополнительно на соответствие определенной длине, и тогда уже вернется Some(конкретная_длина_которую_мы_ожидаем) (или дипазон).


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

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


value wtf is not a member of Option[Main.ArrayFixed[Int, Int(otherLen)]]


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

Ну эти рандомные числа это кишки из JVM

Вспомнил это.


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

5 оно берёт потому что val otherLen = "5".toInt, это легко проверить. А вот как вытащить это на уровень типов — видимо матчингом каким-то, не знаю.

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

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


technic93


Мне не понятно какой тип у ArrayFixed.initDynamic(...), это каждый раз новый тип? Когда там в дженерик аргументах написано i.type, это как понимать?

i.type это некоторый подтип Int, проиндексированный i.

проиндексированный по значению i, а как если оно рантайм или по переменной i? типа что она otherLen, как выше писал Int(otherLen).

а как если оно рантайм или по переменной i?

Оно формально по значению, но у вас нет возможности доказать равенство значений кроме как по is-a (с-но может быть ситуация когда значения одинаковые — а типы разные). В этом и проблема. Если бы было можно — были бы зависимые типы.

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

На самом деле не совсем это, потому что синглтоны в хаскеле давно возможны — собственно, для этого достаточно GADTs. Даже DataKinds не нужны, это синтаксическое удобство (ну как индуктивные типы вместо чёрчеподобной кодировки через ∀), синглтоноподобные кодировки формально ничего от неё не потеряют.


Но да, среднее отношение к синглтонам — «ужас и костыли, пока не завезут нормальные завтипы».


А вообще на самом деле всё очень просто. Есть formation rule, который описывает, какие типы можно реализовывать в данной теории типов. Оно, упрощая, следует шаблону


Г ⊢ T₁ : s₁        Г, x:T₁ ⊢ T₂ : s₂
------------------------------------
         Г ⊢ Пx:T₁. T₂ : s₃

А дальше всё зависит от того, какие s₁ и s₂ допустимы (ну и как определяется s₃).


Если и s₁, и s₂ могут быть только * (то есть, типы), то получается базовое STLC. Можно показать, что П-байндер ни на что не влияет, и переписать тип в привычное T₁ → T₂ со стрелочкой.


Если s₁ и s₂ кроме того оба одновременно могут быть (где * : □), то получится λ_ω (кажется, я всё время забываю формальные названия для этих частных случаев — короче, с типами, зависящими от типов и конструкторами типов).


Если к этому добавить возможность s₁ быть , а s₂ быть *, то получится System F с конструкторами типов.


А если разрешить все комбинации (в частности, s₁ = *, s₂ = □), то мы получим calculus of constructions, то есть, зависимые типы с полиморфизмом и конструкторами типов.


Там везде при этом s₃ = max(s₁, s₂), и там тоже есть варианты, но не суть.


При этом всём DataKinds — это возможность создавать новые сорта с очень фиксированным количеством обитателей, не более.

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

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

Век живи, век учись, никогда его не встречал.


Кстати, звездочки же забанили, теперь надо писать Type, правда?)

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


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

Мне раньше нравилась Scala. Бросил и возвращаться не хочу:


  1. Bloat. Современный софт ужасен в этом плане, и Scala — явный тому пример. Я как-то чинил багу в парсере разметки в Lift Framework. Парсер жил в одном файле строк на 600. Компиляция этого файла занимала пару минут и порождала более 600 класс-файлов, т.е. более одного класса на строчку.


  2. Очень тормозной компилятор, даже с fsc.


  3. Побочные эффекты в неожиданных местах. Многие библиотеки выглядят чисто функциональными, но не являются потоко-безопасными, к примеру. Referential Transparency? Забудте.


  4. Громоздкий синтаксис. Чтобы написать банальный Maybe нужно непростительно много букв и неочевидных фич языка.



Хороший язык должен снижать сложность, Scala эту сложность только преумножает.

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

Это пофиксили в scala 2.12, теперь scala-лямбды компилируются в java-лямбды внутри класс-файла, а не в отдельные классы.


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

А чем стандартный Option не угодил?

Есть подозрение, что вы со скалой очень давно общались. Компилятор достаточно быстрый (если не упороться шейплесом), библиотеки чистые (все эти tapir/rho/typed-schema + finch/http4s + IO/ZIO + doobie/quill + ...), синтаксис в третьей скале куда более читаемый имхо:


trait Functor[F[_]] 
  def [A,B](fa: F[A]) map (f: A => B): F[B]

sealed trait Option[+A]
case object None extends Option[Nothing]
case class Some[+A](value: A) extends Option[A]

given Functor[Option] with
  def [A,B](fa: Option[A]) map (f: A => B): Option[B] = fa match 
    case None => None
    case Some(x) => Some(f(x))

object Main extends App
  println("Hello")
  println(Some(2).map(_*2))
вы со скалой очень давно общались

Последний раз я писал код на Scala в 2013 году.


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

Да, это выглядит лучше. В Scala 2.10 все операции реализовывались через переопределение методов трейта, что выглядело очень громоздко и требовало повторения сигнатур по 3 раза. И всё же Haskell заметно проще, компактнее и приятнее глазу:


class Functor f where
  map :: f a -> (a -> b) -> f b

data Option a = None | Some a

instance Functor Option where
  map None _ = None
  map (Some x) f = Some (f x)

main = putStrLn "Hello" >> print (Some 2 `map` (*2))

В какой-то момент я осознал, что при написанию кода на Scala я на самом деле транслирую свои Haskell идеи в синтаксис Scala. Почему бы не писать сразу на Haskell?

Последний раз я писал код на Scala в 2013 году.

То есть очень давно, я угадал


В какой-то момент я осознал, что при написанию кода на Scala я на самом деле транслирую свои Haskell идеи в синтаксис Scala. Почему бы не писать сразу на Haskell?

Чтобы пользоваться благами жвм, в основном. Библиотеки/рантайм в который вложено просто в 1000 раз больше денег, крутче частичные вычисления,… Вот это всё. А в остальном это действительно про трансляцию хаскель кода на скала синтаксис.




Пока вам отвечал, узнал, что в скала3 появился облегчённый синтаксис:


enum Option[+T] {
  case None       extends Option[Nothing]
  case Some(x: T) extends Option[T]
}

Хуже ML, но лучше чем то, что было

Чтобы пользоваться благами жвм, в основном. Библиотеки/рантайм в который вложено просто в 1000 раз больше денег

Можно же взять eta!

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

Я её сам не пробовал, но, как я понял, это что-то очень близкое к гхц, только темплейт хаскеля нет.


Хотя интересно, как они там без линзочек.

Где же ее работу то на скале найти. Только в Берлине/Мюнхене/Тель Авиве.

Да даже в москве хватает. Плюс, у нас в чатике ходит мем что любая java вакансия это секретная скала вакансия.

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

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

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

На мой взгляд это не выкидывание опыта. Rust молодой язык и явно не для начинающих. Думаю, подавляющее число программистов на нём как раз бывшие C++-ники

Забавно, я слышал про Rust жалобы ровно противоположного характера: мол, среди растоманов програмистов на Rust дофига выходцев из Javascript, Python и Ruby.

Вероятно человек не трогал раст со времен 1.0

Попробуйте программировать микроконтроллеры, опыт он и есть опыт
Мне 40 лет.
Я 3 раза выкидывал свой опыт, начав в 2003-м с Delphi.
P.S. Я и сейчас иногда пишу на D7/FP если надо быстро накорябать что-то оконное и работающее под Windows и Linux. Но клиент как правило уже привык смотреть на результаты через браузер.
Простите-с, а что такое «D7/FP»? Гугл меня не понял (вообще), однако иногда возникает жгучая необходимость «накорябать что-то оконное и работающее под Windows и Linux».
Гугл в такое не умеет. D7 — это Delphi 7 (последняя версия до того, как какая-то муха укусила разработчиков и они ударились в .NET), FP — это FreePascal (хотя думаю, скорее, Lazarus).
Ого, и правда не умеет, удивлен :) Спасибо, не знал что он кросс-платформенный. Возможно и стоит взять на заметочку, но блин, Делфи, даже как-то не знаю. У меня знакомый паскалист есть, 20 000 свои в месяц имеет, говорит больше уже просто не сделать, при этом занимается фотографией и у самого оборудования стоимостью с машину, просто запросы небольшие. Не знаю как он еще не сгорел, владея почти мертвым языком, наверное только благодаря фотографии и держится. Есть тут на Хабре, надеюсь не увидит мой коммент, а-то как-то неловко будет если честно :)
Ну сейчас пошло небольшое оживление. Весь старый код на Delphi теперь с небольшими улучшениями можно применить на iOS/Android. При этом среда разработки в разы шустрее, чем глючный Android Studio. Но всё равно он популярность Android Studio не обретёт.

RAD Studio таки глючнее Android Studio. На больших проектах, например, автодополнение вешает IDE на десяток секунд, после чего ничего не выдаёт. В Idea поиск вариантов дополнения, видимо, делается в другом потоке, и его хотя бы отменить можно.
И плюс глючный компилятор, выдающий internal error без указания, где ошибка.
И, насколько я помню давние попытки товарищей написать что-то на делфи под андроид, hello world имел apk под 15МБ и запускался секунд 5.

Ого, Делфи под Android? Это как это? Компилит код с С и запускает его через NDK, или как? Должны же быть библиотеки андройдоские, а они только сишные, ну под яву само собой.
Делфи уже давно под Андроид, iOS, Mac OS, Линукс и даже веб :)
Под Андроид несколько фреймворков, под веб пяток.
Понятно, только как это работает вообще? Андроид на Яве написан, SDK его понятно тоже, Котлин это по сути Ява и есть, просто немного с другим синтаксисом, а Делфи-то как? Просто транслирует свой код в Яву или С?
Там нативный год генерится, насколько я знаю. Причём не знаю как сейчас, но несколько лет назад он всегда генерился подо все платформы. Только под «не заказанные» — генерилась заглушка, радостно сообщавшая, что платформа не поддерживаются. Пользователи Хромбуков с автоматическим транслятором из ARM-в-x86 (который включался бы, если бы заглушки не было) — были в восторге.
Ого, не, ну похвально конечно если это на самом деле так, и они генерят нативный код в обход виртуальной машины…
P. S. Извиняюсь, я оооочень долго отвечаю на комментарии…
> Это всё равно выкидывание опыта
Раз не хочется выкидывать опыт, и хочется всё же оставаться в C++, оставайтесь. Ездите на встречи комитета C++, заставьте их исправить недостатки C++, которые вы видите (можно через РГ21 пытаться stdcpp.ru ). Участвуйте в проектах (и пишите свои), которые активно используют новые фичи C++, которые фиксят недостатки старых. По поводу UB, возникающих из всяких memcpy: есть подвижки: www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4303.html, en.cppreference.com/w/cpp/utility/launder, wg21.link/P0476. Советую этот блог: blog.regehr.org, автор почти все свои посты посвящает своим попыткам сделать C и C++ точнее (и его компиляторы). Пишет разнообразные новые инструменты для этого. Например, blog.regehr.org/archives/1667, blog.regehr.org/archives/1619, blog.regehr.org/archives/1520, blog.regehr.org/archives/1393, blog.regehr.org/archives/1292, blog.regehr.org/archives/1128, blog.regehr.org/archives/1678, blog.regehr.org/archives/631. Используйте свои знания, чтобы сделать лучше альтернативы C++, такие как Rust. Или создайте свою

Ну вот, кстати, писать тулинг было бы неплохим вложением ресурсов и опыта. Хоть в EDG иди, правда, они на плюсах пишут.


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

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

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


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

А вот с людьми тяжелее, людям всё равно, похоже, нужно уметь читать код в разных стилях, пусть он даже раскидан по разным файлам или модулям или что там.
В ближайшие лет 5-10, в любом случае, толп новичков же не прибавится — всё равно будут те, кто и так уже C++ знают его использовать в основном будут.

А вот лет через 10 можно и компилятор упростить и жизнь людям… заранее предсказать «когда», будет невозможно, конечно, но если это будет включаться опциями компилятора — можно собрать статистику по разным open-source проектам и в крупных компаниях…

Ну, и, в добавление ко всему: сам факт того, что тебе нужно включать «режим устаревшего C++» будет давить — ну не любят люди ретроградами быть, на самом-то деле.

Так что версионирование с явным включением версий — вполне себе разумный путь к «чистке языка». Да, небыстрый… а что вообще в C++ происходит быстро?

У меня есть такое нехорошее чувство, что через 10 лет плюсы будут по большей части там, где много легаси. А как относятся люди к ретроградству в конторах с легаси — вопрос риторический, ИМХО.


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


Короче, я настроен как-то пессимистичнее.

Я тоже по этому пути прошел.
Занимался одним интеграционным продуктом одной известной комании (неважно какой продукт, какая компания). Тоже добрался до топа.
А в конце получилась похожая история, несочетание меня и этой софтвари.
И да, было горестно бростать все нажитое, выученное.
Сейчас получаю давно забытое удовольствие от программирования. python, я ж его просто люблю. Я так соскучился по забытому чувству, когда что-то пишешь-пишешь, а оно бац, и сразу работает. И это с моим-то счастьем (счастье мое в том, что я вылавливаю больше ошибок, чем вся команда. Хотя иногда кажется, что ошибки вылавливают меня.)
Получаю сейчас раза в 3 меньше. И совсем не жалею. Может когда и дорасту до прежних рейтов, может нет, совсем неважно. Убитых лет жалко.
Уверен, что у тебя тоже все получится. Нажитый опыт, он не весь по языку. В этом опыте еще много чего полезного и уникального. Не надо себя недооценивать.
Есть куча свежих, веселых, умных языков, которые решают С++шные задачи.

Есть куча свежих, веселых, умных языков, которые решают С++шные задачи.
А можно примеры? А то ищу, на каком бы языке пострадать фигней в области написания «выскопроизводительного» кода.
Требования просты:
1. Без GC.
2. Без виртуальной машины.
3. C-like синтаксис, но без кучи странных символов в неожиданных местах, как в плюсах.
4. Пакетный менеджер и система сборки из коробки.
5. Кроссплатформенный — должен собираться и работать на MacOS и Linux.
6. Желательно с уже имеющейся библиотекой GUI, не являющейся оберткой над Qt/GTK.
7. Желательно с существующей библиотекой для работы с сетью.
Что смотрел:
1. D — он не взлетел за почти два десятка лет своего существования, посему вкладываться в него нецелесообразно.
2.Rust — перспективно, но пока вроде с GUI там сложно.
3. Swift — пока не разобрался, насколько у него хорошо с Linux, сборкой без Xcode и GUI на Linux.
на каком бы языке пострадать фигней
в области написания «выскопроизводительного» кода.

Попробуйте CUDA или OpenCL, уж страдать — так страдать. Но это, конечно, не универсальные языки. К тому же, очень близкие к С++ и С соответственно.


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

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

Требования немножко противоречивые. 1+2 против 6. Т.е.или низкоуровневый, для системных работ — против UI. Я бы не смешивал.
Если брать первые требования, то Go на ум приходит.


Кстати, зачем С++ выдумали поверх С? Для ООП. А ООП какие проблемы решает? DRY проблемы. Не хочется много раз что-то повторять.
В UI подобные проблемы везде. А в системном программировании поменьше. Linux © — тому хороший пример.

Кто б сомневался, что растоманы в тред набегут :))))

А ещё в тред набежали симаны, сиплюсманы, идрисманы, скаломаны и ещё кто только не набежал.


Мне кажется, у вас избирательная наблюдательность.

Раст — самый любимый язык по опросам на стэковерфлоу за последние несколько лет, и оттуда он не уйдёт :)

И что, людям пишущим/интересующимся одним системным языком в треды других теперь не заходить? Сишники и плюсовики в растотредах присутствуют в количестве, и мне это кажется вполне хорошим явлением. Также как, скажем джависты в тредах про kotlin/scala/clojure/etc или хаскелисты в тредах про scala/rust/idris/coq/etc. Для нормального разработчика вполне здоровая тема интересоваться смежными темами, а не закукливаться в своём болоте.

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

Там ниже писали, что я что-то хорошо и выразительно написал — нет, вот нифига не хорошо и не выразительно.


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



Просто конкретный инструмент надоел.

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

Почему же?


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

Они для вас не основные. В этом разница. Я когда-то давно был TeXником. Разбирался во всех этих стадиях работы TeXа и прочее. Там, конечно, не C++, но что-то похожее, со всеми этим стадиями работы этого чудо-юда.

Ощущение — было близко к тому, что у Вас по отношению к C++.

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

Но теперь я и к C++ отношусь проще и понимаю, что если попытаюсь достичь вот самых-самых вершин мастерств… получу вот ту же самую боязнь и ненависть, что и в случае с TeXом.

Ну как не основные… Дома все пет-проекты почти только на хаскеле, на последней работе хаскеля с плюсами было где-то пополам, на предпоследней были чисто хаскелевские проекты. Да, плюсы, наверное, «основнее», но это отношение скорее где-то 60:40, а не 95:5.


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

> Так и в чем проблема идти в Rust?

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

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


Да что там, у меня в сишарп проектах которые я последнее время пишу 0 (ноль) сеттеров у классов, и никакого наследования. И я бы не сказал что у меня там кривая архитектура получается или я там код не переиспользую. Стало только чище и проще.

Композиция? А в шарпе для неё есть такой же сахар, как в го, когда компилятор сам ищет, к какому классу относится метод?
Композиция? А в шарпе для неё есть такой же сахар, как в го, когда компилятор сам ищет, к какому классу относится метод?

Лучше. В C# есть делегаты и явные интерфейсы.

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

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

Можно, конечно, вспомнить, как это решалось на C. Но необходимость возиться с подобным пугает.
А это-то тут причём? GObject — он вообще о другом. Там поддержка скриптовых языков и рефлексия основную сложность создают.

А если просто писать, придерживаясь COI, то никаких проблем отсутствие «полноценного наследования реализации» не создаёт.
> Ядро Linux — это несерьёзный проект, я так полагаю?

Под «вылазит» я имел в виду «удобно применить».

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

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

Или вот n_tty_ioctl_helper, если не нашёл свой случай, вызывает tty_mode_ioctl с теми же аргументами — почему нельзя это считать аналогом перекрытой виртуальной функции?

> А если просто писать, придерживаясь COI, то никаких проблем отсутствие «полноценного наследования реализации» не создаёт.

Создаёт, когда надо в отдельных реализациях менять только малую часть функциональности.

В моём основном проекте сейчас, навскидку, 3 таких иерархии — гарантированно (альтернатива — массовое тупое дублирование), и ещё 2 — немного лучше выглядят с наследованием, чем со включением.

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

Уровень языка тут к вопросу не относится. А вот специфика проекта — да, в полный рост.

А вот если надо (повторю соседний пример) из 100 методов переопределить только 5 — тут-то и начинается.
Не начинается, а заканчивается. Потому что если у вас 100 методов, из которых нужно перекрыть пять — то у вас отвратительный дизайн и гарантированная куча ошибок. И да, я понимаю почему так сделано в macOS для Macintosh 128K или Windows 1.0 для IBM PC (куда, в оригинальной версии, вставало только 512KiB).

Да, это позволяет получить что-то работающее в очень малом количестве кода… но в современном мире — это очевидный антипаттерн и так делать не нужно. Количество глюков, которые возникают в разных программах из-за того, что никому неизвестно когда какие методы используются и какие «тонкие зависимости» между ними (перекрывая метод A, не забудьте перекрыть также методы B, C, и D… причём все эти описания хорошо если в документации, а не в коде)… вы это всерьёз сейчас предлагаете в язык, построенный вокруг гарантий безопасности тащить?

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

Или вот n_tty_ioctl_helper, если не нашёл свой случай, вызывает tty_mode_ioctl с теми же аргументами — почему нельзя это считать аналогом перекрытой виртуальной функции?
Можно считать. Но это не является наследованием реализации, потому что отсутствует основная «фишка» наследования реализации: возможность меняя поведение функции A «получить в нагрузку» изменение поведения другой, никак визуально не связанной с первой в точке перекрытия, функции B.

В моём основном проекте сейчас, навскидку, 3 таких иерархии — гарантированно (альтернатива — массовое тупое дублирование), и ещё 2 — немного лучше выглядят с наследованием, чем со включением.
Ну да. Выглядит красиво. Иногда. Но… вы можете выбирать — выстрелить себе в руку или в ногу. А попадание в голову — идёт бонусом бесплатным.

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

Уровень языка тут к вопросу не относится. А вот специфика проекта — да, в полный рост.
Вот вообще ни разу. Если у вас в каком-то классе образовалось больше 10-20 виртуальных методов — то это чёткий признак того, то вы что-то сделали не так. И вам нужно или поделить этот объект на части, либо сделать часть методов невиртуальными, а скорее всего — и то и другое: сделать небольшое чисто виртуальных методов в одном объекте и кучу невиртуальных — в другом.

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

Как минимум в одной из упомянутых иерархий это просто факт вот такой вот реальной сложности той сущности. Все попытки упростить её за последние ~10 лет провалились.

Разделить, да, можно. Но это будет на 147% искусственное разделение и в результате будем терять в понятности (начнут возникать неестественные связи) и производительности.

> никому неизвестно когда какие методы используются и какие «тонкие зависимости» между ними (перекрывая метод A, не забудьте перекрыть также методы B, C, и D… причём все эти описания хорошо если в документации, а не в коде)…

Вот именно таких связей, как ни странно:), нет.

> вы это всерьёз сейчас предлагаете в язык, построенный вокруг гарантий безопасности тащить?

Что опаснее — честно перекрыть 5 методов из 100, или копировать реализацию в 10 потомков, а потом, сделав правку, изменить только в 8 из них? Всего-то забыли поменять white_foo() на red_foo()…

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

> Нет, это не вопрос стиля. Это вопрос безопасности. Если вы перекрыли один метод в объекте, где их 100… то вы понятия не имеете, какие методы станут работать по другому. В принципе.

Хорошо бы такие слова подтверждать каким-то реальным обоснованием :)

> Можно считать. Но это не является наследованием реализации, потому что отсутствует основная «фишка» наследования реализации: возможность меняя поведение функции A «получить в нагрузку» изменение поведения другой, никак визуально не связанной с первой в точке перекрытия, функции B.

И как это «не меняет»? Я изменяю обработку в tty_mode_ioctl => изменяется поведение n_tty_ioctl_helper.
В первой никак не написано, что её вызывает вторая. Узнать это можно только сканом по исходникам.

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

См. выше. И я хочу получить таки обоснование больше, чем на уровне «мамой клянусь» :)
Как минимум в одной из упомянутых иерархий это просто факт вот такой вот реальной сложности той сущности. Все попытки упростить её за последние ~10 лет провалились.
100 методов, все перекрываются, но группами по 5? Я хочу это видеть, извините. Вот 100 методов из который 10-20 могут быть перекрыты, а 80-90 — реализовываются через них, а вот такое как у вас… очень странно

Вот именно таких связей, как ни странно:), нет.
Если такого нет, то наследование реализации не нужно. Типажей вполне достаточно.

> вы это всерьёз сейчас предлагаете в язык, построенный вокруг гарантий безопасности тащить?

Что опаснее — честно перекрыть 5 методов из 100, или копировать реализацию в 10 потомков, а потом, сделав правку, изменить только в 8 из них? Всего-то забыли поменять white_foo() на red_foo()…
А кто сказал про копирование? Вам нужно всего-то включить в себя «стандартную» реализацию и сделать 100 однострочных методов, которые будут вызывать «стандартную реализацию».

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

Если что-то забудите пробросить — компилятор возмутится, не бойтесь.

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

И как это «не меняет»? Я изменяю обработку в tty_mode_ioctl => изменяется поведение n_tty_ioctl_helper.
В первой никак не написано, что её вызывает вторая.
Зато во второй написано, что она использует первую. Вот прямо в названии. _helper — это, очевидным образом, обёртка вокруг другой функции. Да и, собственно, нет ничего удивительного в том, что когда ты меняешь более низкоуровневую функцию — более высокоуровневая начинает вести себя иначе. qsort если изменить что с программой на C будет?

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

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

Так и для этого наследование реализации в смысле ООП не нужно.


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

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

Как в вашем подходе перекрытие одной из функций изменит, незаметно для вас, поведение другой функции?
Наследование реализации полезно в строго очерченной ситуации: а)безусловно имеется отношение IS-A и б)есть нетривиальный объем общего для иерархии наследования поведения. Классика — оконный интерфейс.
Когда а или б не выполняются, то наследованию реализации есть лучшие альтернативы.
Классика — оконный интерфейс.
Ага, конечно. И именно потому что оконный интерфейс так хорошо на это ложится в Turbo Pascal 1.0 for Windows имеются для этого отдельные языковые конструкции (не совпадающие с обычными виртуальными функциями), а в MFC или QT — куча макросов и/или других наворотов.

Наслоедование реализации — это костыль. Который позволяет иногда написать чуть меньше кода за счёт разкого уменьшения надёжости этого кода.

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

Это всё равно как жаловаться на то, что в веганском кафе с выбором стейков очень плохо…
Особые конструкции в TP оставим на совести Филипа Кана, они к делу не относятся.
По сути, Вы передергиваете и, простите, впадаете в дискуссионный раж. Или Вы не понимаете, что куча макросов и наворотов в MFC и, прости господи, Qt служат — это особенности подкласса для _той самой унаследовванной реализации_, которая связывает код на C++ с C-вселенной GUI? В ATL/WTL это, кстати, уже сделано на CRTP и выглядит существенно приличнее. Может я и погорячился с однозначно высказанным «полезно*, но то что это *удобно* доказывает тот факт, что ООП, понимаемое как программирование с помощью ключевых слов class, virtual и override получило такое широкое распространение. Если что, я писал оконные процедуры на C, потом писал их с помощью windowsext.h и хорошо помню это наследование „вручную“.
> есть нетривиальный объем общего для иерархии наследования поведения

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

Страуструп когда-то сказал «Наследовать ли самолёт от двигателя? А будет ли у вашего самолёта два двигателя?»

Меня этот вопрос заботит в другом варианте: есть ситуация, когда уже есть нечто, грубо говоря, со 100 методами, а более специфичные реализации заменяют поведение только в малой части аспектов (пусть это будет 5 методов). В текущем проекте есть несколько таких мест. Переписывать их всех на реализацию остальных 95 (опять же условно, но пусть будут такие цифры) пробросом во включённый объект… честно не хочется.
И да, это не оконный интерфейс, это сильно другая задача — хотя последствия сходны.
Ну вот я почему-то наблюдаю такие ситуации сильно чаще, чем обратные — которые и так по умолчанию решаются включением.

Видимо, такая у Вас предметная область. А в другой могло бы быть совсем иначе.
Споры не имеют смысла.
В целом, главная слабость объектно-ориентированного подхода в том, что нужно выделить правильные сущности в предметной области, а это трудно. И требовать такого от прогера-Васи бессмысленно. Хрестоматийный пример о трудности реализации метода Fly у класса Penguin связан с тем, что критерии отнесения объекта к классу Bird не имеют ничего общего с возможностью полета.

А нельзя ли пробросить не 95 а наоборот 5? Или эти 5 отличий из 100 каждый раз разные.

В том и дело, что разные.
Тот факт, что у вас вообще в одном мест собралось 100, потенциально перекрываемых, методов — это плохой признак. Очень плохой. Это значит что вы плохо понимаете что делаете.
Меня этот вопрос заботит в другом варианте: есть ситуация, когда уже есть нечто, грубо говоря, со 100 методами, а более специфичные реализации заменяют поведение только в малой части аспектов (пусть это будет 5 методов).
Разбить на 20 объектов в каждом из которых по 5 методов, основной объект собирается из них. Делов-то.

Если же у вас эти 100 методов «перемешаны» и активно вызывают друг друга…

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

Наследование реализации — это ещё один способ трансформации классического спагетти-кода в нечто, что выглядит как структурный код.

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

И да, это не оконный интерфейс, это сильно другая задача — хотя последствия сходны.
Спагетти-код возникает всегда, когда вы недостаточно чётко представляете себе задачу, которую решаете. Это от типа исходной задачи не зависит.
> Давайте я сейчас напишу тезис, а вы над ним подумаете. Вот только реально крепко, без «да ну, что это за фигня».

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

Iʼve seen some shit, как говорится. Далеко не весь, конечно; но многое. И подтверждения тотальности вашего вывода что-то не нахожу даже с фонарём.

> Наследование реализации — это ещё один способ трансформации классического спагетти-кода в нечто, что выглядит как структурный код.

Если эта претензия на основании соседних примеров с two(), возвращающем 4… ну да, тому, кто привык к структурному программированию, сложно понять все связи в такой конструкции — приходится упрощать.
Но он точно так же будет иметь проблемы, например, от неверно работающего коллбэка. Коллбэки тоже запретитть?

> давайте лучше устроим кашу, когда никто не может сказать какой кусок кода вызывает какой-нибудь другой кусок кода и зачем

Прямо нарушая контракты? Давайте. Только зачем? У меня обычно цель противоположная — чтобы работало :)

Хотя, если задача заставить 1000 условных индусов выдать на гора что-то, с чем они справятся… спасибо, я понял, почему так делают в Go.

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


fn main() {
    let cat1 = Cat { color: Color::White };
    let cat2 = Cat { color: Color::Black };
    let cat3 = Cat { color: Color::Grey };
    let dog = Dog { kind: 2 };
    let animals: Vec<&Animal> = vec![&cat1, &cat2, &cat3, &dog];
    for animal in animals {
        animal.sound();
    }
}

(https://pastebin.com/6gKVFvF5)


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

Потому что нет наследования реализации. Вот этого вот нету:
class GoodBoy {
 public:
  virtual int two() { 
    return 2;
  }
  virtual int four() { 
    return two() + two();
  }
};

class BadBoy : public GoodBoy {
 public:
  virtual int two() { 
    return 4;
  }
};

int foo() {
  BadBoy x;
  return x.four();
}
Когда вы меняете одну функцию — а вместе с этим меняется поведение другой, совершенно визуально не имеющей оношение к первой, функции.

Обратите внимание: BadBoy совершенно никак не трогает фнкцию four, она там даже не упоминается — но она, внезапно, начинает возвращать не 4, а 8. А если бы GoodBoy был написан чуть-чуть иначе — она продолжала бы возвращать 4… а могла бы ешё и 6 вернуть. И она будет это делать даже там, где кто-то работает с GoodBoy& и four и про существование BadBoy, в принципе, не имеет представления.

Эта технология — это «современный GoTo»: отличный способ просто и лаконично отстрелить себе руки, ноги, голову… и другие части тела.

И, насколько я понимаю, в Rust — этой фичи таки действительно нет. Хотя, может быть, я действительно в Rust чего-то не понимаю.

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

Вы нарушили контракт на первую функцию. Вторая её использует — чего вы ещё хотели, нарушив контракт?
Это действительно ваша основная претензия к наследованию? ;)

> Эта технология — это «современный GoTo»: отличный способ просто и лаконично отстрелить себе руки, ноги, голову… и другие части тела.

Вот я перегрузил через LD_PRELOAD или через dlopen() реализацию read(), так, что она зовёт exit() — давайте dlopen() запретим?
А такой случай у меня реально был (символ совпал), в отличие от мифического «two() выдаёт 4».
Вы нарушили контракт на первую функцию.
Возможно.

Вторая её использует — чего вы ещё хотели, нарушив контракт?
Я бы хотел, чтобы изменение поведения кода одной функции не меняло поведение другой, которая с ней никак, в теле программы, не связана.

Вот я перегрузил через LD_PRELOAD или через dlopen() реализацию read(), так, что она зовёт exit() — давайте dlopen() запретим?
А такой случай у меня реально был (символ совпал), в отличие от мифического «two() выдаёт 4».
Мифический? Вы это серьёзно? У вас никогде не было так, что вы перекрывали какой-нибудь метод FillSoket, но ничего не работало пока вы не определите какой-нибудь SocketSize? Или, классика жанра: переопредение equals без изменерия hashCode (классическая ошибка новичков в java)? Извините, но я вам не верю. Вот просто: не верю и всё. Не бывает так.

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

Когда вы расширяете реализацию — никакой информации о том, какие именно методы вы должны перекрывать в тех или иных случаях у вас нет. Кроме, возможно, документации. Но её к компилятору не прикрутишь. И вот это как раз — прямое следствие того, о чём я говорю.
Кажется, я понял Вашу точку зрения: ничем не ограниченное наследование реализации есть зло! Трудно не согласится, Ваш пример тому иллюстрация. Но в C++ есть много чего, что в неограниченном виде есть зло.
Мне лениво пытаться дать точную формулировку для ограничений, надеюсь, все с теме поймут и так. Есть области, достаточно просто устроенные, для которых наследование реализации оказалось полезным. Обычно это, по словам Страуструпа, «не густой лес, а редкий кустарник». Оконный интерфейс — яркий пример.
Мне лениво пытаться дать точную формулировку для ограничений, надеюсь, все с теме поймут и так.
Нет. Не поймут. Я когда-то, ещё школьником, обсуждал эту тему с Шенём. Когда я ещё в школе учился и ООП видел только на древнем, как говно мамонта, Turbo Pascal.

И он мне задал «простой» вопрос: какие ограничения нужно наложить на наследование, для того, чтобы можно было формально доказать, что программа делает то, что она должна делать по спецификации. Я честно пытался что-то придумать, но ничего, кроме «виртуальные функции не вызывают друг друга, а все вызовы осуществляются функцией, используеющей класс» придумать не смог.

А это уже — не наследование реализации. Это наследование интерфейса. Такое и в Rust можно сделать…

Оконный интерфейс — яркий пример.
Скорее контр-пример. Все виденные мною системы имели просто тома, которые объясняли как «правильно» нужно перекрывать те или иные функции и как добиваться того или иного результата. И, в конечном итоге, программы всё равно работали и работают с ошибками и глюками. Всё, что наворачивается в «оконном интерфейсе» (все эти MVC и прочее) — это попытка борьбы с последствиями наследования реализации. Ваша кнопка не должна непосредственно обновлять строку ввода, потому что вы всё равно забудете какой-нибудь из вариантов и вместо этого нужно изменять независимую от кнопки модель — это вот оно.

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

Что самое смешное — в конечном итоге кода, обычно, порождается больше, чем если бы изначально не делалось попыток «срезать углы» за счёт наследования реализации.
Скажем так: возможность перекрывать дефолтные обработчики событий и процедуру отрисовки в производных классах, когда вся «машинерия» взаимодействия с «системой» упростило программирования в стравнении с C API? Меня будет трудно разубедить в этом.
Навороты и нетривиальное взаимодействие — результат неортогональности и в целом противоречивого дизайна как самих оконных систем, так и библиотек и фреймворков.
Все как всегда: нет особых проблем перекрывать четко заданные точки кастомизации поведения, если интерфейс статический. Нет особых проблем перекрывать полностью замкнутые в себе «листья», такие, как обработчики конкретных событий. Менее тривиальные случаи проблематичны и плохо масштабируются при усложнении системы, что усугубляется столь распространенным не самым удачным дизайном с переплетением в одном классе разных consern'ов и т.д. и т.п.
Но это не основание говорить, что наследование реализации  *абсолютное зло* всегда и везде. Это просто неверно: мы спорим благодаря плодам наследования реализации.
Скажем так: возможность перекрывать дефолтные обработчики событий и процедуру отрисовки в производных классах, когда вся «машинерия» взаимодействия с «системой» упростило программирования в стравнении с C API?
Вопрос крайне философский.

Меня будет трудно разубедить в этом.
Не очень понятно в чём и кого требуется убеждать. «Дефолтные обработчики событий» — это и есть наследование реализации. То есть ошибка проектирования. Если вы хотите чтобы ваша программа работала надёжно, то общение с ними — следует минимизировать.

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

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

Но это не основание говорить, что наследование реализации *абсолютное зло* всегда и везде. Это просто неверно: мы спорим благодаря плодам наследования реализации.
Вы недоговорили. Мы спорим на глючащем и тормозящем Web-стайте… это да, с этим согласен. А если бы его не было — могли бы спорить в каком-нибудь FIDO с тем же успехом.

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

Пример в студию?
И он мне задал «простой» вопрос: какие ограничения нужно наложить на наследование, для того, чтобы можно было формально доказать, что программа делает то, что она должна делать по спецификации. Я честно пытался что-то придумать, но ничего, кроме «виртуальные функции не вызывают друг друга, а все вызовы осуществляются функцией, используеющей класс» придумать не смог.

Ну, чисто формально достаточно ковариантности и контравариантности (и выражения семантики в типах, конечно же). То есть, если у вас функция derived : darg1 -> ... -> dargN -> dret переопределяет функцию base : barg1 -> ... -> bargN -> bret, то нужно, чтобы все типы barg_i -> darg_i были населены, и чтобы dret -> bret был населён.

А нет. Речь извините, про конкретный Turbo Pascal, с конкретным таким ООП и виртуальными функциями. Если у вас виртуальные функции вызывают друг друга, то вы должны как-то формально описать — как изменение одной из них влияет на другую. Как это вообще, в приципе, формализовать? Не формализуя, при этом, полностью так же и всю реализацию? Сказать что фукнции не вызывают друг друга — это понятно. А вот если вызывают — то вам уже просто языка не хватит, чтобы что-то формально описать. Да, наверное, можно такой язык разработать… но это будет, во-первых, нифига не просто, а во-вторый — выйдет далеко за рамки понятий Turbo Pascal.
Перефразируя «Собачье сердце»: Друг мой, не вызывайте виртуальные функции из виртуальных функций! )))))
«Просто не делайте ошибок»? Это подход C++. В Rust — принято делать так, чтобы ошибочные конструкции было писать сложно, а правильные — легко.
В Rust — принято делать так, чтобы ошибочные конструкции было писать сложно, а правильные — легко.

С первым соглашусь, со вторым — не уверен). Какое это имеет отношение к реализации виртуальных функций-членов производных классов в C++?! Мы же не о разных языках говорим, а о конкретной фиче конкретного языка.
То, что это стоило бы запрещать компилятору — согласен. Но Страуструп такой мягкий человек, а крик поднимается такой всеобщий…
То, что это стоило бы запрещать компилятору — согласен.
Сейчас это уж поздно запрещать. Да и изначально было проблематично. Наоборот — если ранние книги про OOP почитаете, там эта возможность как преимущество преподносится. В каком-нибудь Turbo Vision (это прямо от разработчиков самого Turbo Pascal) вы перекрываете функцию GetViewPort, чтобы другие функции того же объекта начали по другому работать.

И то же самое всё — в MacOS Classic, Windows и так далее. Ну что за побочными эффектами такого подхода нужно следить — во всех учебниках написано. Только вот как за этим сделать — не написано.
Ну, создания Филипа Кана для меня никогда не были авторитетными. А то, что возможность комбинаторного взрыва проглядели — печально. Можно было бы потребовать явного квитирования вложенного виртуального вызова, но это а)полумера и б)не было бы радикальным решением. Да и заботы у комитета другие…
Да и заботы у комитета другие…
О да.

Вот этот вот примерчик вспомните, да. Вызов виртуальной функции из конструктора — вызывает не функцию потомка, как в Java или том же Turbo Pascal, а вовсе даже функцию предка. Даже в том случае если все эти функции — в разных единицах трансляции. И это — стандартизованное и документированное поведение. Причём, ради этого, наворотов в компиляторе приходится устраивать… изрядно. Но тут они думали только о том, что функция потомка может, внезапно, обратиться к частям объекта, которые ещё не сконструированы. И вот чтобы тут устроить «типа безопасность» — потребовались дополнительные правила.

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

А некоторые и до сих пор этого не поняли.
Как поциент-поциенту. С++ тебя не отпустит, бежать куда-то с него бесполезно. Он тебя везде найдет. Всё, что ты можешь сделать — это создать «лучший С++», победив тем самым его в самом себе. Примерно так. Извини, что «на ты». На брудершафт не пили, знаю.
У нас контора с плюсов расползается: половина на питон, другая половина — на js.
После С++ пересесть на JS??????? Да вы в своем уме? Все, что не нравится в плюсах, это еще цветочки по сравнению с JS-ом. Хуже него только 1С
Пишите на С, там нет 90% головной боли, которую придумали себе разработчики С++.

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


А так можно и до ассемблера дойти, там нет 100% головной боли, которую себе придумали в более высокоуровневых языках.

Безопасность в С зависит только от ваших знаний и умений. Код на нем *более* поддерживаем, чем на С++, потому что миллионы темплейто-генерированный-лямбда-переопределенных операторов читаются много хуже нормального кода на С.
Шаблоны на 5-7 минут генерации в С просто бессмысленны. Если вам так уж захотелось сгенерировать одинаковых функций для разных типов — то возьмите XMacro. Но если вы забрались так глубоко то с вашим кодом что-то не так.

Насчет читаемости и понимания:
У меня на работе 116 000 строк сишного кода в основном репозитории. Пока не было случая, чтобы новичок приходя в команду спрашивал, что конкретно делает вооот этот блок кода.

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

Ну да, чтобы писать код без багов, достаточно просто 100%-ой концентрации 100% времени у 100% программистов, работающих над проектом. Делов-то.


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

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


Шаблоны на 5-7 минут генерации в С просто бессмысленны. Если вам так уж захотелось сгенерировать одинаковых функций для разных типов — то возьмите XMacro.

Нет, мне хочется не этого.


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


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


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


Как это сделать на С с такими же характеристиками производительности?


У меня на работе 116 000 строк сишного кода в основном репозитории. Пока не было случая, чтобы новичок приходя в команду спрашивал, что конкретно делает вооот этот блок кода.
Зато один единственный С++ проект, является кладезью непонятных мест и сомнительного кода. Кроме того этот 1500-строчный проект компилируется почти столько же, сколько остальной код.

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

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

То есть вы сравниваете откровенный говнокод на С со структурированным С++ и заявляете, что С-плохой? Почему такой код попал в репозиторий проекта? У вас нет ревью кода? Бегите оттуда.

Ну так с тем вашим проектом на плюсах точно то же самое.


Кстати, вопрос про то, как сделать ту штуку с графами на С, был абсолютно серьёзным, мне это правда интересно.

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

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

UFO landed and left these words here

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

UFO landed and left these words here

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

Это проблемы раста, такое было даже на примитивных итераторах.

При чем тут раст? Это был пример к высказыванию "инлайнинг все равно ни на что не влияет". В плюсах такой же эффект будет, пример автора с wc и то как там несмог компилятор заинлайнить std::min тому пример.

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

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

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

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

Там далеко не всё так однозначно с std::min.
Во первых, Вы точно про wc а не про подсчёт расстояния Левенштейна? Во-вторых, если да то дело там совсем не в инлайне.

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

Там много ньюансов.

Для MSVC 2005 они таки важны. А Chrome долгое время этой версией собирался.

Для современных clang, gcc и, вроде бы, даже MSVC — inline уже не нужен. Но есть ньюанс.

В заголовочном файле

inline ... foo(...) {
  ...
}


нужно менять, внезапно, не на
... foo(...) {
  ...
}
а вовсе даже на
static ... foo(...) {
  ...
}


И вот по историческим причинам люди предпочитают не
использовать static в заголовочных файлах — даже если речь не идёт про MSVC.

Окей, не буду спорить. В случае раста развешивание #[inline] / #[inline(always)] / #[inline(never)] на функциях вызывающихся в горячих tight loops вот прям очень сильно влияло на производительность.


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

Просто не думаю, что гцц настолько всемогущ что его эвристики всегда идеально определяют, надо инлайнить или нет.

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

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

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


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

Хотя «Без интринсиков и оставаясь исключительно в рамках стандарта? Тогда проигрыш в 2-3 раза.» (ассемблеру) – наверное все же нет

Это про автовекторизацию. С ней всё плохо.

Можно. Можно даже руками сделать весь этот инлайнинг, сведя сложность написания кода от O(n) к O(n!) (потому что теперь вы просто руками прописываете те варианты, которые раньше за вас генерировал компилятор).


Темплейты, конечно, ад, но не настолько, чтобы это было проще.

Кстати, вопрос про то, как сделать ту штуку с графами на С, был абсолютно серьёзным, мне это правда интересно.

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

Кроме Си/C++ есть ещё и третий язык для расчётов:
Fortran. Думали — Питон, Луа, Матлаб,… — нее!!!

И вообще-то можно взять другую модель задачи и по-другому её формализовать, и тогда решение будет плохим на С++, и потребуется какой-нибудь Erlang/Elixir.

Для глубокой оптимизации м.б. особое железо, GPGPU, FPGA, assembly language, переписывание сетевого стека и частей ОС на asm + AVX2, ОСРВ и прочая наркомания, которой можно позаниматься за слишком большие деньги.

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

Это не расчёты. Да, конечно, там внутри в графе есть расчётные узлы, и там как раз шаблоны не нужны, и их там нет. Шаблоны нужны, чтобы это всё потом склеить вместе.


Но просто так, из любви к искусству, шаблоны применять не стоит. Был у меня другой проект, где надо было закодить быстрый random forest руками — там нет никакой вариативности задачи, поэтому никаких шаблонов, никакого ООП, просто один файл строк на 300-500 с несколькими функциями, которые просто перемалывают числа.

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

Это уже сильно не «точно так же».


Ну и писать компиляторы я люблю (не зря мы там обсуждали DSL для этого всего), но писать на С парсер/оптимизатор/кодген я бы точно не стал. Зачем? Есть куда более приятные языки для этого.

Такие вещи решаются кодогенерацией по модели соответствующей предметной области. Но это отдельная магия с повышенными требованиями к спецификациям.
Поддержу.
Есть большой внутренний сишный продукт, где вручную написано ООП (наследование, полиморфизм, перегрузка) и куча диких макросов, потому что иначе 99% кода будет обёрткой над 1% логики. Это к тому, что Си многословен для высокоуровневых действий. Наверное, 20 лет назад плюсы не годились для эмбеда, поэтому выбрали си.

И есть драйвера 300-500 kloc от разных восточных друзей, с функциями на 10-20 kloc. Это к тому, что, программисты, способные написать сложный драйвер, не способны сделать это хорошо.
Представьте себе, что вы делаете фреймворк для построения графов обработки данных
Как это сделать на С с такими же характеристиками производительности?

  1. Та же самая кодогенерация;
  2. Ренейминг символов сборочным скриптом перед линковкой + LTO;
  3. JIT через библиотеку;
  4. Обработка графа батчами с шедулером — так ещё и быстрее будет за счёт локальности кешей и возможности применения векторных инструкций.
Та же самая кодогенерация

А кодогенератор на чём? И почему бы генерировать не LLVM IR, скажем?


Ренейминг символов сборочным скриптом перед линковкой + LTO

Я, если честно, даже не представляю, как это будет работать. Особенно если у вас что-то вроде


if (monday)
  runGraphA();
else
  runGraphB();

JIT через библиотеку

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


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

В этой задаче баланс между latency и throughput очень сильно смещён в сторону latency.

Как это сделать на С с такими же характеристиками производительности?

Это достигается до определенной степени макросами и всякими хитростями. В ядре такой код я видел. Вообще хоть на С и сложнее добиваться полиморфизма и inline, язык просто на порядок проще и примитивнее. По количеству строк будет реально не в разы больше. Код будет тупее, но будет меньше всяких конструкторов копирования, move, запрещения копирирования. Или, например, сравите насколько читаем какой-нить STL/boost или linux kernel.
Но это моя очень субъективная оценка.
Из очень производительного и хорошо читаемого С++ кода был http-сервер с мой позапозапрошлой работы github.com/yandex-load/phantom — он довольно легко работал в 2-3 раза быстрее nginx на то время. Автор принципиально не использовал ни строчки из STL из-за непредсказуемых аллокаций и всяких лишних указателей на parent в std::map, например.
По количеству строк будет реально не в разы больше.

По моим оценкам лучший способ сделать это на С — не делать это на С, а кодогенерировать С.


Автор принципиально не использовал ни строчки из STL из-за непредсказуемых аллокаций и всяких лишних указателей на parent в std::map, например.

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


Но вот какой-нибудь std::find_if или std::enable_if вполне себе норм, они точно ничего не аллоцируют.

а чисто ради спортивного интереса — можно где нибудь этот «gstreamer на С++ шаблонах» пощупать?

Увы, нет. Это очень внутренняя штука.

Ну да, чтобы писать код без багов, достаточно просто 100%-ой концентрации 100% времени у 100% программистов, работающих над проектом. Делов-то.


достаточно
1. юнит-тесты писать
2. написать и оттестировать ядро системы, где есть опасные моменты, в Си это в основном работа с памятью. Если есть ядро, которое будет оттестировано на 100%, будут защищены все места работы с памятью, то концентрация необходимая при использовании данного ядра может быть уже не 100%.

И всегда покрывайте написанный код юнит и интеграционными тестами, каким бы гуру вы не были. Это сократит в дальнейшем время на поиск багов многократно.
  1. юнит-тесты писать
  2. написать и оттестировать ядро системы, где есть опасные моменты

"В ядре Linux, разыменование указателя перед его проверкой на NULL позволило компилятору удалить эту проверку, создав уязвимость в системе." © как раз статья про UB


Nerf Unit test this. В смысле — нет. Не достаточно.

Очевидно что достаточно иметь юнит тест который этот NULL туда будет передавать. И это UB прекрасно всплывает, так же как проблемы в десятке других мест где проверки на NULL просто нет.

А как вы будете проверять ситуацию, когда какой-то код не проверяет указатель на NULL и так и надо, потому что проверка происходит выше по стеку?

Если функция не ожидает на вход null то я сделаю ее аргументом ссылку, а не указатель. И тогда это up to caller проверять что он не разыменует null перед передачей аргумента дальше.

Во всех остальных случаях проверка на null нужна.
А если по ссылке передается объект, в поле которого сидит указатель, и выше по коду уже была проверка этого поля на nullptr?

Просто assert(obj->field)?

if (!obj->field) return?

/* obj->field isn't supposed to be null here */?
Прямого доступа к полям объекта быть не должно. Должна быть геттер-функция (от этого можно отойти для простых структур, но указатель явно к таким не относится). Если предполагается что она всегда должна вернуть валидный объект то она должна возвращать ссылку, делать внутри себя проверку на null и кидать исключение если что не так. Если предполагается что кейс где поле нулевое валиден, то геттер возвращает nullptr а получающий его код сам решает как обработать null.

Это всё, конечно, хорошо, но мы вроде как о C говорили, а там ссылок нет.

Там без вариантов — проверять на null везде, либо вводить какие-то naming convention по принципу «если имя указателя заканчивается суффиксом _safe то можно его не проверять» и надеяться на то что ее все будут соблюдать. Неплохая иллюстрация для того почему C++ намного лучше C.
К сожалению не понял что Вы хотели сказать своим примером.

Это уже больше похоже на "необходимо": "1. Юнит-тесты писать. На всё. Покрывая полностью".


Что, кстати, возвращает к пункту "100% концентрации 100% времени у 100% программистов" в подварианте "… пишуших код и юниттесты к нему".
То есть хорошее дело. Но та-же проблема — делают его неидеальные люди.

Зависит от того что Вы хотите получить. Чем больше усилий затрачено — тем сильнее гарантии качества, причем как обычно гарантия 99.9% требует на порядок больше усилий чем гарантия 99%. Даже небольшое покрытие юнит тестами уже дает неплохие гарантии и ловит много багов, но для sensitive кода где ошибка недопустима естественно тестов должно быть больше.
Вот один из классических примеров:
тут 3 итерации, потом условие переполнения говорит «нефиг проверять i<10, потому что мы знаем, что i<3» (итого 112 итераций)
— а тут из того же условия i<3 вытекает «мы не будем продолжать цикл при i>=3» (3 итерации)

совершенно минимальное изменение метода работы с циклом.
А теперь представьте себе, что вы ловите это юниттестом внутри цикла.
Программа стабильно выдает неправильный ответ => вполне себе ловится подходящим юнит-тестом. А вообще -fno-strict-overflow должен быть, имхо, включен в любом вменяемом компиляторе по умолчанию :).
> А вообще -fno-strict-overflow должен быть, имхо, включен в любом вменяемом компиляторе по умолчанию :).

Ну вот сейчас вам на это несколько участников, начиная с khim, расскажут о ценности оптимизаций, когда можно заменять a+1>a на true и тому подобное :) (и я видел цифры, что во многих случаях это даёт ~20% прироста скорости)

А от себя я добавлю, что если -fno-strict-overflow означает -fwrapv, то я считаю это преступлением — по умолчанию должно быть -ftrapv или его аналог с выставлением TLD флага, а контекстными опциями уже можно ставить генерацию исключения (C++), враппинг, «расслабление» как сейчас в C/C++, и прочие локально необходимые режимы.
О чём вы? -ftrapv в GCC не работает уже лет 10 (если не больше) — и «всем пофиг».

Из всех этих «опций безопасности» прилично работает разве что -fno-strict-aliasing… и то потому, что на него куча проектов ипа V8 завязаны.
> -ftrapv в GCC не работает уже лет 10 (если не больше) — и «всем пофиг».

Предположим, что работал бы. Хотя я предпочту флаговую переменную.

> Из всех этих «опций безопасности» прилично работает разве что -fno-strict-aliasing… и то потому, что на него куча проектов ипа V8 завязаны.

Это тоже поле для регулирования.

Хочу напомнить, что в SQLite тестов на пару порядков больше, чем собственно кода, и 100% тестовое покрытие, но баги там до сих пор находят.

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

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


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

Безопасности там ещё меньше

А нужна ли она здесь? Пока вы сжигаете месяцы на борьбу с теорией типов и ограничениями компилятора, идеальная команда из сишника, (пен)тестера, девопса и исследователя:


  • Почитает man gcc, добавит полезных warning-ов, FORTIFY_SOURCE, stack protector и т.д.;
  • Подумает про модель угроз и распилит монолит на разные процессы;
  • Напишет политики песочниц уровня ОС (seccomp, selinux);
  • Напишет fuzzing тесты на входные данные;
  • Вынесет стейт в БД, организует backup процесс для восстановления после креша;
  • Настроит сбор мониторингов, логов и крешдампов с прода (feedback loop);
  • Настроят сбор и мониторинг CVE-шек сторонних зависимостей;
  • Проверят инварианты взаимодействий (протоколов) в model checker-е (TLA+, Tamarin и ко).

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


нереализуемы вообще никак, кроме кодогенерации

Т.е. всё таки реализуемы.

А нужна ли она здесь?

Как по мне, безопасность лишней не бывает. Особенно, когда она бесплатная. Как пример бесплатной безопаности можно взять Maybe вместо "Любая ссылка может быть null". Абсолютный зирокост, платите максимум столько же, чаще — меньше.


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

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


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

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

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


идеальная команда из сишника, (пен)тестера, девопса и исследователя: …

И всё это каждый релиз!


Т.е. всё таки реализуемы.

Вне языка. Кодогенерировать С я могу хоть из хаскеля, хоть из питона, хоть из DSL'ей типа Ragel'а. Когда вы пишете на Ragel, вы же не пишете на С?

А нужна ли она здесь?

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

Скажите пожалуйста, а в сторону Delphi современного вы не смотрели? Предвосхищая вопросы из разряда «у тебя с головой в порядке?» скажу, я совсем не программист, мой максимум это python и то «just for fun», ну и при небольшом напряжении мозга смогу сконвертить число с одной в другую систему счисления. Просто недавно читал что C/C++ в 90 — 00 вылезли больше из-за политической коньюктуры (в США не хотели зависеть от языков разработанных европейцами и вроде в мире «Паскаля» на самом деле всё очень сладко.

Про дельфи уже за меня хорошо написали тут.

Просто недавно читал что C/C++ в 90 — 00 вылезли больше из-за политической коньюктуры (в США не хотели зависеть от языков разработанных европейцами
При том что основным разработчиком и поставщиком компиляторов Паскаля еще с 80-х годов была чисто американская фирма, ага :)

Когда сломался я, то оставил себе право на небольшие либы на чистом Си, и только в тех местах, где нужны инструкции SIMD и почти-уже-ассемблер, и только до 10-20 тыс строк кода.


Но у меня было не только выгорание, но еще и хронические проблемы с руками отчасти из-за многословности как чистого С89/С99, так и С++.

А так можно и до ассемблера дойти


Если у вас действительно критичные по времени приложения, то ассемблер на фиксированном железе + ОС реального времени по трудоемкости вполне могут уже быть альтернативой вашим боданиям с языком С++.
С это конечно хорошо, и круто, однако вакансий — раз, два и обчелся. И в основном в embedded, где мало платят и нельзя удаленно.
Тоже поддержу. При кропании на С, по большому счёту «мины» я сам расставляю )).

А как там с UB? Я давно на нем не писал, но по-моему все очень плохо.

Стандарт языка C99 занимает 514 страниц с указателем. С++17 — 1440. Большее количество функционала — большее количество ям.

UB можно избегать если соответствовать стандарту разработки типа MISRA (пусть даже частично) + использовать адекватные анализаторы.
Ага. Нотки то простреливали твои в комментах, как чуял что то зреет. Так ты дошел до социального комфорта представлений большинства и очутился в подлиной реальности. ты делал то что в представлении других было правильным? Боишься были ли не твои желания?

Я вообще ничего не делал, а по большей части плыл по течению. По крайней мере, каких-то нетривиальных решений я не принимал. Делал либо то, что было интересно и драйвово (вот эти вот ночи за кодом), либо то, что делают «все» и что делать «хорошо» ­(поступить в вуз, написать диплом, принять вкусный оффер, пойти в аспирантуру, впрочем, успешно её провалив).


Но это уже совсем другая тема и совсем другая история.

UFO landed and left these words here

У меня похожая история: IT-кружок, отличник-медалист, универ, аспирантура, ктн, стажировка за границей, карьера.


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


После нескольких подзатыльников от жизни стал задумываться: "А что же, собственно, не так, я же весь такой хороший и всё, вроде бы, делаю правильно?"


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


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

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

Я не изучал плюсы, у меня нет такой Огромной проблемы с разнообразием их возможностей. Моим миром было игрушечное ООП в JVM. Паттерны, SOLID вот эта вся фигня. И в какой-то момент я крайне в ней разочаровался ибо, вот учит человек паттерны, знает все буковки в солид, а код продолжает писать как курица лапой. Единственное для чего реально эти штуки пригождались — ответить на собеседовании на вопрос «что означает буква O в Solid?» или сказать кому нибудь «Ты нарушил принцип L» (почему-то люди вокруг понимают этот аргумент лучше, чем «ты внес ломающие поведение изменения»). С паттернами тоже интересная штука. Когда я читаю требование: есть объекты N типов, я совершенно не ожидаю увидеть код, где есть фабрика фабрик визиторов визиторов погоняемая билдером стратегий.

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

Собственно этим и стал заниматься, очень интересное и развлекающее занятие. Но, повторюсь, у меня JVM, так что мин на моем поле скорее всего много меньше)
ИмператорRUST защищает вас от большинства UB на этапе компиляции (не от всех, но вздохнуть спокойно можно). Автор, попробуйте поиграться именно с ним. Вероятно с вашим «сишным» прошлым, будет не сложно начать делать какие-то интересные и мб, даже, приносящие доход, штуки. Ну и классные ништяки из коробки, по типу быстрых хэшмап, zero cost итераторов и тд, буквально дают второе дыхание при разработке на этом ЯП)

Вставлю свои пять копеек насчёт Rust:


  • вариадических дженериков нет
  • higher-kinded дженериков нет (есть generic associated types, но до стабилизации далеко)
  • higher-ranked polymorphism есть, но в таком урезанном виде, что ты этой фичи не заметишь
  • const generics aka non-type generics parameters нет (что приводит к существованию вот таких вот костылей)
  • специализации нет
  • генераторов нет
  • аналога decltype нет
  • аналог constexpr есть, но очень урезанный
  • обобщённых лямбд (специализирующихся по месту вызова) нет
  • static assert нет, только эрзац с фиговыми сообщениями при ошибке компиляции
  • кастомных аллокаторов для коллекций нет (можно переопределить аллокатор только глобально)
  • создать самоссылающуюсю структуру можно, но через Pin, документация по которому съест тебе мозг чайной ложечкой

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


И ещё кое-что, что уже не столь однозначно плохо, но всё же заставит поломать голову при портировании архитектуры:


  • перегрузки нет
  • ООП нет
  • исключений нет (но есть паники, которые, если прищуриться, похожи на исключения)

А так — да, неплохой современный язык, всем рекомендую.

(что приводит к существованию вот таких вот костылей)

Ожидал по ссылке typenum, навёл — увидел typenum в урле. Забавно, как в совсем разных языках костыляются одни и те же вещи.

Уже добавили в RFC


Одна из прелестей Rust в скорости его развития обществом

Одна из прелестей Rust в скорости его развития обществом

А не приведёт ли это Rust к текущему состоянию C++?

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

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

То есть новые фичи в С++ не такие качественные, как хотелось бы?

Фичи уже не те.


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


struct Foo
{
    Foo() = delete;
};

Foo foo {}; // валидный код до C++20
В моём понимании это означает, что фичи, которые так взаимодействуют с инфраструктурой — недоделанные. Комитет так много требований выдвигает, что бы пропозалы не интерферировали со старыми — и всё равно выходит УГ. Хотелось бы C++2, но, я думаю, его не будет, — время упущено. Для C++2 нет места — там уже Rust и Haskell, их догнать будет сложно. C++2 надо было вместо C++11 делать, но некому было.

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

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

Ну это нарушения принципа явное лучше не явного. Типа сэкономили пару строк синтаксического мусора, но увеличили нагрузку на программиста.

Не, это просто «не досмотрели».


Когда пару лет назад на одном из митингов комитета по стандартизации об этом случайно зашла речь, и Вилле (chairman core working group) провёл эксперимент, написав и попробовав скомпилировать этот код (то есть, уверенности не было), то для значимой части сидевших в аудитории это поведение было сюрпризом (напомню на всякий, что это заседание комитета по стандартизации языка).


Но это так, мелочи.

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

Нет, это просто комплятор ещё не дозаточили.

P.S. Без сагказма, кстати: развитие C++ идёт по спирали. Комитет по стандартизации придумывает новые zero-abstraction фичи, вписывает в стандарт, а потом лет 5-10-20 разработчики компиляторов рвут на себе волосы пытаясь их сделать zero-abstraction. Последнее достижение — корутины. Теоретически они тоже могут быть zero-abstraction в некоторых случаях… практически, я думаю, до этого компиляторы если и дорастут — то где-нибудь к 2030му году…

Вот только RFC датируется 2017 годом и когда дойдёт до реализации непонятно. Если что, я пишу как раз на расте (и перебрался с С++), но многие фичи, перечисленные AnthonyMikh, есть в RFC, найтли или хотя бы в виде issue. Вот только ресурсы команды разработки не безграничны и не факт, что их приоритеты совпадают с тем, что нужно/хочется лично вам.

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

"This is left as an exercise for reader."


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

Прекрасный список! Большинство из перечисленного в той или иной степени готовности или запланированности и это само по себе здорово! Над всем перечисленным можно поработать и при этом код нужно будет писать на Rust — вот он dog-fooding.

GAT крайне сырой, специализация падает с ICE стоит чуть-чуть отойти от примеров из issue, конст-генерики тоже, отлажены только сценарии которыми пользуется кортима,… Так что степень запланированности тут весьма условная. Что поделать — у кортимы нет ресурсов, все силы брошены на сахарный асинк-авейт и залатывание дыр в пине. Приоритеты конечно вызывают грустное удивление, но тут мало чего можно сделать.

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

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


Эх, где этот avoid popularity at all costs.

Да, понимаю. Те же чувства про раст сейчас.

Rust 2, Haskel 2. Все любят приводить пример питона как негативный, а по мне, так это успех! Стратегические цели развития языка важны, но полностью игнорировать тактические потребности пользователей языка нельзя, иначе ресурсов не хватит довести разработку до пригодного для использования состояния. Все эти популярные асинки нужны для того что бы привлечь ресурсы в экосистему для больших вещей. Да, потом за это придется платить, но хотя бы будем чем платить.

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

Да, тут как раз из моего дистрибутива линукса не так давно вынесли сотню-другую пакетов, которые остались на python 2, и на третий не перешли.


Сейчас вот выпиливают пакеты, застрявшие на python 3.6 и не поддерживающие 3.8 (что я не очень понимаю, ну да ладно, я не спец в питоне).


Да, потом за это придется платить, но хотя бы будем чем платить.

Ну вот плюсы тоже платят.

плюсы тоже платят.
Не платят, плюсы решили вечно пилить ветку 1.0. Нет оплаты — нет желаемой минимизации smaller, simpler, safer language
вынесли сотню-другую пакетов, которые остались на python 2
Ну и норм, Python не умер. Мне кажется, это лучше, чем вечное легаси. Чем раньше его ампутировать — тем проще. Во в плюсах затянули, что теперь только по шею ампутировать :).
плюсы решили вечно пилить ветку 1.0
Ну совсем так я думаю сказать нельзя, например auto_ptr.

Эх, когда уже в хаскеле будет адекватное версионирование пакетов без dll hell… stackage конечно старается, но шаг в сторону — и здраствуй резолв зависимостей руками и браузером.

Был же раньше stack solve, но его что-то убрали в stack 2.x.

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

Без GAT невозможно доделать async/await, он нужен для трейтов, а то так и будем жить в куче.

По поим ощущениям async-await сильно переоценен. Мало того что он сожрал столько ресурсов, так он ещё и нестабильный, шаг влево — ICE, шаг вправо — пины повылазили. Простой пример — попробуйте рекурсивную async/await функцию написать (я пробовал когда писал статьи про загрузку дерева комментариев). Сразу пины-боксы, всё вот это вылезает.


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


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

Да не особо. Помнится, большинство людей было против "?" оператора, но тима сказал "нам плевать, мы считаем что так лучше". Ну и правы оказались, конечно. Они вообще часто правы, но не всегда. Но поинт в любом случае в другом: мнение сообщества учитывается, но не более того. Где-то читал прямую речь от лодочника или ещё кого-то из главных чуваков, что мы сами принимаем решения. По-моему после обсуждения асинк-авейта как раз, но уже точно не помню.

БТВ, на TypeScript писал недавно на Angular без всяких asyn await. Реактивно с pipe() и subscribe(). Прекрасно себя чувствовал.
Мы в wg-traits почти дотащили chalk MVP в rustc, который как раз блокирует и гаты (над ними поэтому никто и не работает особо, потому что без chalk нет никакого смысла), и конст дженерики, и еще много чего, так что скоро™ все будет, не отчаивайтесь
По поводу наследования — даже в играх его предпочитают не использовать и вместо этого делать композицию (ECS). В Rust вполне себе легко сделать ECS. Лично меня отталкивает в Rust это отсутствие монад. Были бы монады и do нотация было бы просто замечательно. А так, ну его. Есть и по приятней языки с которыми я получу больше удовольствия от работы.

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

Хм, извиняюсь. Мне казалось я видел как ты где-то делал примеры кода на расте… Наверное перепутал с кем-то другим

Эййй… пссс… Delphi :)

Опытный программист на C++ легко решает любые не существующие в Паскале проблемы.(с) ГанСмоукер вроде сказал, но я не уверен.

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


Delphi, Pascal — ну вообще не про это.

Ну зачем менять плюсы на мозилловский/гугловский/борландовский ЯП?

В Delphi свои проблемы. Большая часть из которых решена в том же С++.

Отсыплю примерчиков.


Как выглядит ФП на современном делфи (да, я писал мини-библиотеку для всяких "нормальных" вещей типа, хм, динамических массивов с capacity. TList сам ведь не освобождается):


var arrayOfB := arrayOfA.Map<TB> (function(v: TA): TB
  begin
    result := someObject.someMethod(v);
  end);
// или, как вариант, чтобы не аллоцировать интерфейс-лямбду каждый раз,
var func: TFunc<TA, TB> := function(v: TA): TB
  begin
    result := v.someMethod(someObject);
  end;
var arrayOfB := arrayOfA.Map<TB>(func);

Отмечу не только убогий автовывод (представьте вместо TA, TB какой-нибудь TDictionary<SomeClass, TList>) типов, но и то, что подобный код может криво подсвечиваться их же IDE, криво форматироваться их же IDE, а иногда приводить к внутренним ошибкам компилятора. Для справки: IDE и компилятор разрабатывает одна фирма.

Как выглядит тот же код на более хорошем языке:


val arrayOfB = arrayOfA map (_.someMethod(someObject))
// или, во втором случае,
val func: B => A = v => v.someMethod(someObject)
val arrayOfB = arrayOfA map func

Таких примеров кривости языка, даже не относящиеся к ФП, можно набрать минимум с десяток.


Ну взять хотя бы тот факт, что class, который с конструктором и деструктором, всегда аллоцируется в куче, а record, который может содержать конструктор с параметром, но не может содержать конструктор без параметров или деструктор, всегда аллоцируется на стеке (или внутри class, record или массива). При этом и в class, и в record поля определённых типов (интерфейсы, строки, массивы) таки инициализируются и финализируются компилятором, т.е. конструкторы по-умолчанию и деструкторы для record есть, но написать код для них язык не позволяет. Поэтому RAII на record'ах (т.е., на стеке) реализовать невозможно. А если использовать "более штатное" RAII на интерфейсах, addRef/releaseRef для которых вызывается автоматически, то, во-первых, нужно писать отдельный интерфейс для каждого класса, т.е. писать заголовки методов трижды (в интерфейсе, в классе, в реализации), и, во-вторых, при создании объекта и запросе интерфейса выделяются 2 области памяти в куче (что в некоторых случаях приводило к просто эпической фрагментации виртуальной памяти). Ну ещё лишний jmp при доступе к методам, но это уже можно не считать.


А вот ещё. Уж не помню, есть ли в delphi volatile переменные, но, когда я изучал этот вопрос (d7), такого ключевого слова не было, и, по-видимому, все переменные были volatile. Впрочем, для x86 это ничего не меняло. Ну либо все переменные были не volatile, но многопоточность всё-таки работала, поэтому склоняюсь к первому варианту, хотя достоверно не знаю.

Беспараметровые конструкторы record уже на подходе. Также должен появиться языковой сервер (?) в IDE, который через LSP (Language Server Protocol) будет реализовывать подсветку и проч. ништяки в IDE. Многословность дженериков ну да, есть такое. В некоторых диалектах Паскаля (Oxygene) заимствовали синтаксис С#. Дельфисты же более консервативны в этом плане.

уже на подходе

Ну наконец-то.
Но язык не идёт в ногу со временем.
inline var с автовыводом вон тоже были "на подходе" аж до 2018 (?) года, когда уже и древние С++, и Java давно осилили автовывод типов переменных.

А вот ещё. Уж не помню, есть ли в delphi volatile переменные, но, когда я изучал этот вопрос (d7), такого ключевого слова не было, и, по-видимому, все переменные были volatile.

В D7 да, все было volatile, но это, простите, 18 лет назад было. В новых поколениях ввели такой, достаточно странный синтаксис:

[volatile] MyVariable: TMyType;

Почему не более привычная для делфиста форма

MyVariable: TMyType; volatile;

не понятно.
Ну код на Дельфи гораздо более понятен, чем в «нормальном языке». Если полгода не смотреть программу, вы потом будете долго сквозь этот нормальный код продираться.
Если полгода не смотреть программу, вы потом будете долго сквозь этот нормальный код продираться.

Такого не происходит. Конкретно мой пример элементарен, как "a = b + 1", это практически идиома. И он ровно настолько же понятен, как "a = b + 1".
Вот если там хотя бы 3-4 вложенных map-filter навернуть, то уже трудночитаемо будет. И многословность тут вряд ли спасёт.

Непроходимо глючные IDE и компилятор. Ребят, 10 лет как появились лямбды, в значения захваченные переменные при отладке так и не показываются — это что? Выкатили inline variable c выводом типов, но забыли, что managed-type нужно файнализировать? Один и тот же код под Win64 выдает "[dcc64 Hint] Variable '' is declared but never used in ''", а под Win32 такого хинта нет?


Неконсистентность, непоследовательность языка. Почему дженереки не работают для свободных функций, где тайп хелперы для джененик типов/интерфейсов, что за метания со способами управления памятью, zero/one based strings и т.д. Такое очущение, что ребята не знают куда идти. Сбоку фичу прикрутили и ладно.


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


Одна из базовых абстракций во всех языках — последовательность данных, итераторы. Взяли, тупо скопировали из .NET. Только из-за различий в системе типов, в дельфи иерархия IEnumerable/IEnumerable даже подстановку Лисков проваливает. Уж не говоря о том, что из-за багов компилятора эти интерфейсы даже реализовать нельзя нормально. :facepalm:
И так везде. Не, на времена Delphi5 — это был нормальный язык. Но сейчас он сильно устарел с одной стороны, а попытки его подтянуть до современного уровня больше похожи на костыли.

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

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

Шутки ради swift умеет весьма упоротые compile time штуковины, хотя конечно даже до раста ему далеко.

Дети это сплошной ub в недоверенной среде похлеще чем у js в IE при этом на разогнанной процессоре на котором происходят ошибки в расчетах, с постоянным состоянием гонки. Еду миллионами ппюроцессов, с околонулевой изоляцией памяти, постоянными инъекциями как в код так и в данные при этом без возможности перезагрузки и полного перехвата линий ввода — вывода

>Кто я, если выкинуть плюсы?
Давайте взглянем на это с другой стороны.

Задачи за которые платят, редко ставятся как «нужно закодировать на C++, Java, ассемблере… вот эту вот хреновину». Задача — решить проблему бизнеса. Для этого надо понять проблему, и возможно — переформулировать проблему. А потом выразить ее каким-то понятным железке способом. И никого при этом не волнует, на каком языке вы это реально напишете. А знание тонкостей одного конкретного языка — это хорошо если 10% решения.

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

Это умение решать проблемы очевидно есть, и оно никуда от вас не денется. Вы можете взять любой инструмент, пусть тот же Rust, или хаскель, и решить проблему с их помощью. Или даже взять ассемблер — почему нет? Или javascript. Умение выбрать нужный инструмент из многих — часть той самой высокой квалификации. Умение применить к задаче нужную математику — тоже признак высокой квалификации. Это все останется, если выбросить C++. И это совсем не мало. Возможно, будет чуть труднее показать работодателю уровень своей квалификации.

Я не понимаю, как это работает.


То есть, у меня на практике как-то задачи бизнеса всегда имели оговорки типа «у нас уже есть система на плюсах, для которой нужно сделать новый компонент». Или «нужно разработать новую систему, но её пользователями будут программисты на C++». Наверное, дело в том, что я больше всего занимался чем-то вроде инфраструктурных проектов.


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


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


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