LINUX.ORG.RU

про то, как видеть C++

 , ,


2

3

Ещё не выздоровел до конца после темы с воспалением легких и легко устаю, и вот посреди одного доклада по C++ на конференции я натурально уснул, и во сне приснилось удивительное.

Проснувшись я стал смотреть на синтаксис C++ и видеть его сквозь призму того, что читал о Haskell (никогда не программировал на нём, а только писал хэлловорлды), и своего небольшого опыта со Scala - всякие scalaz, cats, итп.

Если в глазах иметь своеобразный фотошоп, который выбрасывает из синтаксиса C++ __Уродливые_Идентификаторы и [квадратно](гнездовые) -> конструкции, то на поверхность проступает красота и логичность происходящего. Ты видишь аппликативные функторы и произростающие из них монады, которые просто томятся в застенках из покосившехся скобочкек и отсутствия базовых вещей вроде каррирования.

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

Есть подозрение, что разработчики стандарта это понимают, принимают C++ как язык общего назначения (а не только для написания низкоуровневых системых вещей), и улучшают синтаксис и стандартную библиотеку с целью минимизации в необходимости этого выверта восприятия. Вполне возможно, через десяток лет на C++ будет так же просто писать, как на Haskell или Python. А сейчас придётся ну, самостоятельно заниматься расширением сознания

Подскажите, верно ли моё восприятие? Как двигаться в этом направлении? Нужно ли мне углубляться в Haskell параллельно с изучением C++?

★★★☆☆

Ответ на: комментарий от anonymous

 Тебе уже сообщили, что никакие «ленивые» говновычисления в императивщине нахрен никому не упали

Так и запишем: ranges, которые так просят включить в стандарт C++, никому не нужны и вообще измышления теоретиков, оторванные от реальной жизни. Ну а чо, если последовательность нужно отфильтровать, отобразить и свернуть — нужно лепить циклы с тремя промежуточными векторами, ибо нефик!!!!1!!

anonymous ()
Ответ на: комментарий от anonymous

Это зависит от того, как ты воспринимаешь haskell.

Многое переняли. Один из трех столпов haskell наряду с ФП и статикой - это классы типов. Из них очень и очень многое перетащили в rust под названием traits (правда, некоторые уважаемые программисты путают эти traits с другими traits из других языков). Уже за одни traits можно памятник ставить создателям rust. Теперь сравним с тем, что предлагает здесь C++: шаблоны на шаблонах, бр-р.

А концепты в C++ вообще какие-то чумные писали по сравнению с изящной реализацией traits constraints для генериков в rust, взятых, кстати, тоже из haskell. Ну, это просто продолжение тех же классов типов с учетом полиморфизма.

dave ★★★★★ ()
Ответ на: комментарий от dave

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

anonymous ()
Ответ на: комментарий от anonymous

Так и запишем: ranges

Ты, балабол, опять всё перепутал. ranges не имеют никакого отношения к ленивости и к фп-параше. Они были тыщу лет в крестах. Основная фича, ради которой всё делается - это заменить (begin, end) на условный (range).

никому не нужны и вообще измышления теоретиков, оторванные от реальной жизни.

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

Ну а чо, если последовательность нужно отфильтровать, отобразить и свернуть

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

— нужно лепить циклы с тремя промежуточными векторами, ибо нефик!!!!1!!

Именно так это и работает в твоей фп-параше.

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

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

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

anonymous ()
Ответ на: комментарий от dave

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

Теперь сравним с тем, что предлагает здесь C++: шаблоны на шаблонах, бр-р.

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

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

Таким образом это второй слой типизации. Когда вместо f(a, b) {return a.call(b.call());} - ты будешь заново типизировать всю парашу.

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

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

Что нужно сделать для трейтов? Проверить один раз в декларации «есть ли у этих трейтов нужные методы»(даже тут уже смешно, т.к. в крестах есть не только методы).

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

Кстати, это целиком и полностью паста с классиков в любом пхп, там используется подобная схема. Только даже она куда мощнее.

anonymous ()
Ответ на: комментарий от anonymous

Фееричное жидкое в лужу

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

anonymous ()
Ответ на: комментарий от anonymous

Извини, всю писанину не смог распарсить. Только для читателей замечу, что трейты в rust могут компоноваться как динамически (с небольшим оверхедом на переход по косвенной адресации через vtable как в случае вызова виртуального члена-функции в C++), так и статически (без какого либо оверхеда и каких либо обращений к vtable). Весь мой опыт на haskell говорит о том, что в haskell тоже самое происходит с классами типами, где главное правильно расставлять прагмы INLINE и/или INLINABLE там, где есть код, зависящий от class constraints. Так мы сымитируем на haskell тот же monomorphization из rust.

dave ★★★★★ ()
Ответ на: комментарий от dave

Извини, всю писанину не смог распарсить.

Слишком слаб.

Только для читателей замечу, что трейты в rust могут компоноваться как динамически (с небольшим оверхедом на переход по косвенной адресации через vtable как в случае вызова виртуального члена-функции в C++)

Как там, уже printf осилили сделать без vtable?

так и статически (без какого либо оверхеда и каких либо обращений к vtable).

Без vtable -это не статически, к тому же тебе говорили не про это. Тебе говорили, что это второй уровень типизации, а все трейты - чистая динамика. То, что в каких-то случаях это можно свести к статике - это всё происходит не на уровне языка, о чём тебе и сообщили.

Весь мой опыт на haskell

Опыт ваяния лаб?

говорит о том, что в haskell тоже самое происходит с классами типами, где главное правильно расставлять прагмы INLINE и/или INLINABLE там, где есть код, зависящий от class constraints. Так мы сымитируем на haskell тот же monomorphization из rust.

Мне, да и всем остальным, как-то насрать на всё, что там происходит в каком-то недоязычке для запартных изваяний.

Как удобно ты съехал с темы на какую-то херню. Кукарекал про шаблоны, обосрался - начал ретранслировать хаскель-методички.

anonymous ()
Ответ на: комментарий от dave

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

Что такое растовские запартные трейты? Это пхп. Тут тебе сложно будет понять, чем твоё пхп отличается от не-пхп, но я тебе помогу. Давай я тебе объясню на примере сишки.

Все твои генерики - это типы вида void *. Хочешь ты написать T add(T a, T b); - ты напишешь void * add(void * a, void * b); Т.е. генерик это не тип - это некая абстрактная сущность, которую можно прочитать/записать.

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

Таким образом, к void * add(void * a, void * b); - добавляет некий список трейтов/интерфейсов, которые определяют набор методов, доступных вызова. Как я уже говорил выше - ты просто накидываем интерфейсы на void *.

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

Для реализации нам потребуется чуть расширить void *, добавив туда мета-информацию, а именно список трейтов/интерфейсов.

А далее всё просто - мы создаём трейт «есть оператор +», T = void *; накидываем его на T в void * add(T[«есть оператор +»] a, T[«есть оператор +»] b) { a + b; }

Здесь нам нужно проверить только одно - есть ли в T[«есть оператор +»] - operator+. Проверить это без инстанцирования - его тут и нет, т.к. T так и осталось void *

Далее мы хотим передать туда данные с T[«есть оператор -»]. Для проверки типов нам ненужно знать ничего, кроме сигнатуры. Если в сигнатуре требуется: T[«есть оператор +»], а у нас T[«есть оператор -»] - ошибка. Если T[«есть оператор -», «есть оператор +»] - всё нормально.

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

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

Да, можно наколхозить схему типа «кресты», т.е. клонировать тело функции заменяя условный void * на T. Но это лишь убогий колхоз.

anonymous ()
Ответ на: комментарий от anonymous

Хорошо, раскрою свою мысль. Шаблоны C++ и traits из rust связаны вот в каком сценарии. Берем асинхронные вычисления Future. В новом C++ это делается через шаблон future. В rust это делается через параметризованный trait Future. По сути это монада и там, и там.

Так вот, если ты вызовешь в rust метод and_then (это аналог монадической связки), то если это не trait object, то метод вызовется без использования vtable. Вызовется как обычный невиртуальный метод. Если бы у метода была директива inline, то тело метода даже заинлайнилось бы. Когда ты используешь trait object, то начинается уже динамика.

В haskell примерно то же самое (не буду говорить, как там называется аналог trait object, а то совсем разволнуешься). Еще при использовании class constraints возникает динамика, если нет прагм, хотя тебе же haskell не интересен.

dave ★★★★★ ()
Последнее исправление: dave (всего исправлений: 2)
Ответ на: комментарий от dave

Шаблоны C++ и traits из rust связаны вот в каком сценарии.

Никак не связаны.

Берем асинхронные вычисления Future. В новом C++ это делается через шаблон future.

Не делаются. Новый С++ - это типа 10лет назад? Хорошо, через шаблон.

В rust это делается через параметризованный trait Future. По сути это монада и там, и там.

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

Так вот, если ты вызовешь в rust метод and_then (это аналог монадической связки), то если это не trait object, то метод вызовется без использования vtable.

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

Если бы у метода была директива inline, то тело метода даже заинлайнилось бы.

Очевидно, что это днище убогое не умеет ничего инлайнить.

Когда ты используешь trait object, то начинается уже динамика.

Она там начинается везде, даже в printf"е. Внимание вопрос - почему.

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

Шаблоны - это чисто статическая вещь. Она семантический статическая, всегда. В ней них нет типизации, в них нет ничего.

Трейты - это чисто динамическая херня, из мира пхп. Она семантический динамическая. В ней есть типизация, причём это второй слой типизации.

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

А знаешь почему так? Потому что, если бы это была не прикрученная сбоку семантика, а реальная, то ты бы мог использовать это.

Т.е. если у тебя есть функция <T>f(x: T) {x.foo();} - это должно работать, т.к. если у тебя на уровне семантики происходит клонирование(инстанцирование) функции, то она должна иметь вид: f(x: Foo) {x.foo();} - здесь тип однозначен, мы можем использовать всё то, что доступно в Foo.

Но у тебя этого нет, потому что семантика динамическая, а «инстанцирование» попросту прикручено сбоку(если оно вообще есть).

anonymous ()
Ответ на: комментарий от anonymous

Что сказать то хотел? Что за днище ты линканул и что из него следует?

Из этого следует, что ты хреново знаешь Rust, но мнение имеешь :)

http://blog.metrink.com/blog/2018/03/30/rust-trait-objects-vs-generics

P.S. Набрасывать надо тоньше :)

htower_ ()
Ответ на: комментарий от htower_

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

anonymous ()
Ответ на: комментарий от Legioner

Для начала выкинуть семантику #include-ов, хотя бы в отношении новых исходников. Пишешь там в заголовке файла module vector и в твой уютный файлик уже не пролазят никакие сторонние define-ы. Только те, которые ты заимпортишь

Это уже сделано в ЯП семейства Паскаля. Интересно, как там дела с современными версиями Оберона?

LongLiveUbuntu ★★★★★ ()
Ответ на: комментарий от anonymous

Зачем ты пытаешься вводить какие-то сектантские левые базворды, если они к делу отношения не имеют?

Ага, зато ПТУшная феня имеет. И это… man «Вывод типов», чтобы не поминать почём зря PHP и динамику.

be_nt_all ()
Ответ на: комментарий от LongLiveUbuntu

как там дела с современными версиями Оберона?

Да как-то хреновато. Вирт — на пенсии, на OberonCore.ru в основном мучают открытый дюжину лет назад BBCB, в частности научили его работать на linux под Gtk (но это всё ещё бета), но так и не научили быть 64-х разрядным.

Ну есть ещё Виртовский Oberon-07 (можно почитать на том же OberonCore) и вот такая https://miasap.se/obnc/ реализация. Правда на этом самом Core вместо obnc предлагают нечто проприетарное (зато от отечественных обероноводов).

be_nt_all ()