Статическая типизация не обязательно требует церемоний

Автор оригинала: Mark Seemann
  • Перевод

Примечание переводчика: в текущий момент я подготавливаю материалы для обещанной статьи по монадам. К сожалению, это занимает довольно много времени, не говоря о том, что я всё же должен заниматься основной работой и уделять время семье, но процесс идёт. А пока представляю вам перевод небольшой свежей заметки от замечательного товарища Mark Seemann'а, которая мне показалась любопытной.


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


Церемонность


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


Церемония — это то, что вы делаете до того, как начнете делать то, что вы действительно собирались сделать.

Venkat Subramaniam

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


Это привело меня к мысли о том, что существует злосчастная Зона Церемонности:



Конечно же, эта диаграмма всего лишь упрощение, но я надеюсь, что она демонстрирует суть. C++, Java и C♯ — языки, которые требуют церемонности. Справа от них находятся языки, которые мы могли бы назвать транс-церемониальными, включая F♯ и Haskell.


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


Малое количество церемоний в JavaScript


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


> consume ([1,2,3], 1);
[ 2, 3 ]
> consume ([1,2,3], 2);
[ 3 ]
> consume ([1,2,3], 3);
[ 3 ]
> consume ([1,2,3], 4);
[]

В первом случае мы удалили только первый элемент, тогда как во втором и третьем мы удалили и 1, и 2, потому что сумма этих значений 3, а запрошенный quantity был 2 и 3 соответственно. В четвертом примере мы удалили все элементы, потому что запрошенный quantity был равен 4, и нам нужно просуммировать все числа, чтобы сумма стала достаточно большой. Функция должна работать строго слева направо, поэтому мы не можем взять только 1 и 3.


В JavaScript эта функция могла бы быть реализована примерно так:



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


Большое количество церемоний в C♯


Давайте сравним пример на JavaScript с кодом на C♯. Та же самая функция на C♯ могла бы выглядеть так:



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


Но на самом деле всё еще хуже. Код выше работает только с int массивами. А что, если мы хотим использовать long?


Нам придется написать еще одну перегрузку:



Вам нужна поддержка shortов? Еще одна перегрузка. decimal? Еще одна. byte? Еще одна.


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


Малое количество церемоний в F♯


Ту же самую функцию в F♯ можно написать так:



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


quantity: ^a -> (seq< ^b> -> seq< ^b>)
  when ( ^a or  ^b) : (static member ( + ) :  ^a *  ^b ->  ^a) and
        ^a : (static member get_Zero : ->  ^a) and  ^a : comparison

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


> consume 2 [1;2;3];;
val it : seq<int> = seq [3]

> consume 2m [1m;2m;3m];;
val it : seq<decimal> = seq [3M]

Статическая типизация означает только то, что вы не можете вызвать её с произвольными значениями. Выражение вроде consume "foo" [true;false;true] просто не скомпилируется.


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


Малое количество церемоний в Haskell


Аналогично вы можете написать эту функцию в Haskell:



И снова вам не нужно указывать никаких типов. Компилятор просто их выведет. Вы даже можете спросить у GHCi о типе функции, и он вам выдаст:


> :t consume
consume :: (Foldable t, Ord a, Num a) => a -> t a -> [a]

Оно выглядит чуть более компактно чем выведенный в F♯ тип, но суть остается той же. Оно скомпилируется для любого Foldable контейнера (В том числе и об этом в следующей статье, прим. пер), и для любого типа, принадлежащему тайпклассам Ord и Num. Num поддерживает сложение, а Ord — сравнение.


Как вы можете видеть, в F♯ и Haskell требуется довольно мало церемоний, однако оба языка остаются статически типизированными. Более того, их система типов мощнее, чем у C♯ или Java. Они могут выражать такие взаимоотношения между типами, которые эти языки не могут.


Резюмируя


В спорах о статической типизации против динамической, участники обычно обобщают их опыт с C++, Java или C♯. Им не нравится количество церемоний, требуемое в этих языках, но они ложно считают, что отсюда следует, что не бывает статически типизированных языков без церемоний.


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


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

Средняя зарплата в IT

120 000 ₽/мес.
Средняя зарплата по всем IT-специализациям на основании 6 277 анкет, за 1-ое пол. 2021 года Узнать свою зарплату
Реклама
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее

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

  • НЛО прилетело и опубликовало эту надпись здесь
      +10

      Не понял, при чем тут коробки и всё остальное. Типы никуда не делись, просто их писать необязательно. Про то, что вывод типов удобнее чем их явное написание спросите у сишарпистов. Я помню как Java-разработчики запрещали в шарповых проектах var, потому что неочевидно.


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


      consume :: (Foldable t, Ord a, Num a) => a -> t a -> [a]
      consume quantity = reverse . snd . foldl go (0, [])
        where
          go (acc, ys) x = if quantity <= acc then (acc, x:ys) else (acc + x, ys)

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

        +6

        просто это выглядит плачевно на некоторых случаев. Если ты указал, что i = 0, не трудно бы догадаться, что вероятнее всего там будет int. И эти догадки чаще облегчают жизнь.


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


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

          +2
          не трудно бы догадаться, что вероятнее всего там будет int

          или uint, или string но ты кавычки забыл
            0
            или string но ты кавычки забыл

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

            или uint

            А вот такое вообще должно остаться только в языках «близких» к железу, вроде С/С++
              +2
              А вот такое вообще должно остаться только в языках «близких» к железу, вроде С/С++

              А вот я не согласен. Конечно, uint хуже, чем Natural[32], но это явно лучше, чем
              // Эта функция не принимает отрицательные числа! Иначе кинет ошибку
              0
              Тогда код не должен скомпилироваться дальше, например при попытке вызвать конкатенацию у числа и или при сложении знакового и беззнакового.
              0
              Так же динамическая типизация помогает в случаях, если функция занимается вызовом других функций, в зависимости от их типов (для тех языков, где не придумали конструкции как в Swift, которая действительно удобнее)

              А какие конструкции придумали в свифте? Ниразу свифт не щупал, но интересно что там с функциями интересного "придумали" :)

            +3

            А какие в С++ церемонии именно касательно типов? Auto и шаблоны уже давно к услугам автора. Имхо на С++ вполне себе можно писать в питоник-стиле.

              +2

              Ну на самом деле на плюсах довольно вербозно. Например, возьмем функтор списка:


              class Functor f where
                fmap :: (a -> b) -> f a -> f b
              
              instance Functor [a] where
                fmap f (x:xs) = (f x) : (fmap f xs)

              и плюсы


              template<template<class> F, class A, class B>
              F<B> fmap(std::function<B(A)>, F<A>);
              
              template<class A, class B>
              std::vector<B> fmap(std::function<B(A)> f, std::vector<A> v) {
                std::vector<B> w;
                std::transform( std::begin(v)
                  , std::end(v)
                  , std::back_inserter(w)
                  , f);
                return w;
              }

              Но вообще плюсы да, наверное чуть попроще в чем-то. Но в чем-то наоборот сложнее.

                0

                В моих проектах более-менее давно используются элементарные альтернативы грядущим ranges. Я обычно именую такую библиотеку pythonic или pt. Записать код выше более-менее симпатично без старой вербозной идиоматики итераторов вполне возможно. Да, нет одной стандартной замены. Это немного удручает.

                  0

                  И с std:: function так не стоит. Компилятор не соптимизирует, будет неоправданное замедление.

                    +2

                    Ну, с новым стилем главное тоже не упороться :)


                    И с std:: function так не стоит. Компилятор не соптимизирует, будет неоправданное замедление.

                    К сожалению, я не очень близко знаком с плюсами. Буду иметь в виду, спасибо.

                      +1

                      Да, это точно. Я потому и избегаю ranges по Ниблеру, так как куда-то это не туда зашло. :) Это бич С++, что в итоге все пишут какую-то свою метабиблиотеку, которая у всех примерно об одном, но в стандарте её нет. Зато есть всякое другое, иногда сломанное.

                        0
                        К сожалению, я не очень близко знаком с плюсами. Буду иметь в виду, спасибо.

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


                        Все остальные аннотации типов в 95% случаев несут только dx-назначение — поднять ошибки подстановки выше.

                      +3
                      и плюсы

                      Вот плюсы:


                      auto fmap(auto f, auto v) {
                        return v | transform(f) | to<std::vector>();
                      }

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

                        +6
                        auto fmap(auto f, auto v)

                        Ух ты, C++20.


                        И да, вы потеряли ограничение, что v должно быть вектором с элементами типа T, а f — функция из этого же T в некий U. Опционально можно указать, что возвращается вектор с элементами типа U.


                        В плюсах все интерфейсы созданы из расчёта производительности.

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

                          0
                          Ух ты, C++20.

                          Он там не особо нужен — после fmap можно добавить = [] и будет не С++20. Да и в чем проблема с С++20?


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

                          Попытка оптимизации одного приведёт к ограничению других оптимизаций. Это всегда будет компромиссом. Это не является чем-либо значимым без полного сравнения с интерфейсами в других языках.


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

                            +6
                            Он там не особо нужен — после fmap можно добавить = [] и будет не С++20.

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


                            Да и в чем проблема с С++20?

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


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


                            Попытка оптимизации одного приведёт к ограничению других оптимизаций.

                            Во-первых, ваш же исходный тезис был, гм, немного другим.
                            Во-вторых, что именно оптимизирует вытаскивание некоторых кишок std::unordered_map наружу, да ещё и не очень удачных? Я, наверное, ни разу не видел проекта, где этим бы пользовались (или, вернее, где это было бы необходимо).

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

                              Я об писал выше. Это не выглядит плохо — это не выглядит сильным аргументом. Покажите для начала альтернативы приведённого функционала в F#/hs.


                              К тому же, сами тезисы не очень верные. Перегрузку добавить можно композицией. Более точные указания типов добавить можно(это тоже С++20, но это не концепты — это уже давно есть). sfinae — покажите в других языках альтернативы.


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

                              Не вышел стандарт? Поделитесь стандартом упомянутых автором перевода hs/scala/rust/F#? Либо хотя-бы F#/hs.


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


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


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

                              Это проблемы этого продакшена. Нас они волновать при сравнении не должны. К тому же, существует много продекшена на чём угодно. Другое дело, что у С++ продакшена тупо больше. А какого-нибудь F#/rust легаси вообще нет.


                              Во-первых, ваш же исходный тезис был, гм, немного другим.

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


                              Во-вторых, что именно оптимизирует вытаскивание некоторых кишок std::unordered_map наружу, да ещё и не очень удачных? Я, наверное, ни разу не видел проекта, где этим бы пользовались (или, вернее, где это было бы необходимо).

                              Я не совсем понимаю. Вы ссылаетесь на то, что вы(или кто-то ещё) хотел написать более оптимальную хешмапу для stl, но она оказалась не совместима с интерфейсом? Зачем эта более оптимальная хешмапа в stl? Её можно использовать без проблем без каких-либо проблем — это С++.


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

                                +7
                                Покажите для начала альтернативы приведённого функционала в F#/hs.

                                Какого именно? auto auto auto или рейнджей?


                                Перегрузку добавить можно композицией.

                                Как? Чтобы fmap работал и с optional, и с определённым в сторонней библиотеке типом, например, бинарного дерева. Или хешмапы (только по значениям, естественно).


                                sfinae — покажите в других языках альтернативы.

                                Они там не нужны. Туда завезли, например, тайпклассы.


                                Не вышел стандарт? Поделитесь стандартом упомянутых автором перевода hs/scala/rust/F#? Либо хотя-бы F#/hs.

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


                                Поддерживающий компилятор есть.

                                Как он называется? А то ни кланг, ни гцц не поддерживают C++20.


                                Отсылки к продакшену так же не засчитываются. Потому как используется уже много где. Да и отсылки к продакшену на фоне F#/hs так же несостоятельны.

                                Делюсь исключительно личным опытом. В месте, где в проде в 2016-м году только перешли на C++11 (прописью: одиннадцать), и при этом в том же 2016-м был в том же проде хаскель, я работал. В месте, где в 2019-м году в проде был gcc 6.1, не поддерживающий C++17, из какой-то там центоси, я тоже работал. Хаскель и свежайшая версия питона там тоже были.


                                Это проблемы этого продакшена. Нас они волновать при сравнении не должны.

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


                                А какого-нибудь F#/rust легаси вообще нет.

                                ну вот забавно, что плюсы у вас легаси.


                                Вы ссылаетесь на то, что вы(или кто-то ещё) хотел написать более оптимальную хешмапу для stl, но она оказалась не совместима с интерфейсом? Зачем эта более оптимальная хешмапа в stl? Её можно использовать без проблем без каких-либо проблем — это С++.

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


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


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

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

                                  –3
                                  Какого именно? auto auto auto или рейнджей?

                                  Того, на который вы ссылались. sfinae/перегрузку/decltype


                                  Как? Чтобы fmap работал и с optional, и с определённым в сторонней библиотеке типом, например, бинарного дерева. Или хешмапы (только по значениям, естественно).

                                  В чём проблема? Лямбда — это просто оператор(). Композиция лямбд — композиция этих операторов. Всё так же как и просто с перегрузкой оператора().


                                  Они там не нужны. Туда завезли, например, тайпклассы.

                                  Покажите пример того, о чём говорили. Допустим, перегрузка по decltype.


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

                                  Раз шильдик неважен — не говорите о нём.


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

                                  Основания.


                                  Как он называется? А то ни кланг, ни гцц не поддерживают C++20.

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


                                  Делюсь исключительно личным опытом.

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


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


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

                                  Это не проблема не языка, а тех, кто даёт такое. С таким же успехом можно хаять скалу/котлин за то, что на 95% прода дадут жаву.


                                  ну вот забавно, что плюсы у вас легаси.

                                  Для меня легаси не плюсы. Я нигде такого не говорил. Для меня легаси — это легаси. А на плюсах оно или на пхп — мне без разницы.


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

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


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

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


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

                                  Какие ваши доказательства?

                                    +5
                                    sfinae/перегрузку

                                    Реализуется через тайпклассы.


                                    decltype

                                    Не нужно в виду неявной типизации.


                                    В чём проблема? Лямбда — это просто оператор(). Композиция лямбд — композиция этих операторов. Всё так же как и просто с перегрузкой оператора().

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


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


                                    Покажите пример того, о чём говорили. Допустим, перегрузка по decltype.

                                    Я не знаю, что вы имеете в виду под перегрузкой по decltype, потому что общепринятого такого термина нет.


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


                                    Если вы имели в виду SFINAE и проверку на соответствие какому-то концепту, то вместо ad-hoc-костылей вы, ну, просто пишете тайпкласс.


                                    Раз шильдик неважен — не говорите о нём.

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


                                    Основания.

                                    Да.


                                    Никакого С++20 теймплейты в лямбдах не требуют.

                                    Это кусок C++20. Значит, темплейты в лямбдах требуют C++20.


                                    Можно доказать это ещё более формально, но не хочу быть занудой.


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

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


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

                                    Я вот прям вчера дёргал библиотеку, которая возвращает std::unordered_map из чего-то в чего-то. Правда, её название вам вряд ли о чём-то скажет.


                                    Или вот та же nlohmann/json умеет работать с std::unordered_map, а не с аналогами от folly, abseil или чего там.


                                    Какие ваши доказательства?

                                    Титусовское вообще легко гуглится: вот.


                                    Ресёрч на тему совместимости какой-нибудь там open addressing пополам с cuckoo hashing с unordered_map гуглится чуть сложнее, но их несовместимость — это уровень фольклора (в математическом смысле), не надо писать статьи, чтобы показать, что 2+2 ≠ 5.

                                      –8
                                      Реализуется через тайпклассы.

                                      Покажите.


                                      Не нужно в виду неявной типизации.

                                      Т.е. нельзя? Никакая явная типизация не может, потому как она не сможет в такое decltype(a + b)


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

                                      С чего вдруг? Не понимаю зачем вы спорите на тему С++, если все ваши познания, судя по всему, ограничены каким-то древним легаси? Композиция ламбд достаточно старый паттерн и о нём знают все, кто что-то знает о современных плюсах.


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


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

                                      Если мне нужно передать имя — я заюзаю лямбду/типа. Точно так же я могу передать его через лямбду. [](auto ... args) { return f(args...); } — так.


                                      Я не знаю, что вы имеете в виду под перегрузкой по decltype, потому что общепринятого такого термина нет.

                                      (auto a, auto b) -> decltype(f(a, b)) перегрузка для случая, когда тип данного выражения выводится. Т.е. функция есть и оно возвращает значение какого-либо типа. Реализуйте тоже самое "не на костылях".


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

                                      Нет, это не основной документ. Это просто то, что есть у С++ и чего нет у раста/хаскеля и прочего. Нет смысла ссылаться на стандарт в ситуации, когда у оппонента его нет.


                                      Да.

                                      Оснований нет, хорошо. Зачем о чём-то пытаться рассуждать, если оснований нет?


                                      Это кусок C++20. Значит, темплейты в лямбдах требуют C++20.

                                      Никто не измеряет стандарты в компиляторах какими-то С++20. Зачем вы, опять же, рассуждаете о том, в чём не разбираетесь? Там половина компиляторов вообще никакой стандарт "полностью" не поддерживает.


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


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

                                      У меня другой опыт. Какой смысл ссылаться на чей-то опыт?


                                      Я вот прям вчера дёргал библиотеку, которая возвращает std::unordered_map из чего-то в чего-то. Правда, её название вам вряд ли о чём-то скажет.

                                      И что?


                                      Или вот та же nlohmann/json умеет работать с std::unordered_map, а не с аналогами от folly, abseil или чего там.

                                      json особенно nlohmann — это прикладуха. Какой смысл ссылаться на nlohmann, который вообще не имеет никакого отношения к производительности?


                                      Титусовское вообще легко гуглится: вот.

                                      И что? Это документ 2 месячной давности. Очевидно, что никто там за 2 месяца ничего и нигде не изменит.


                                      К тому же, этот документ про abi. И там даже явно написано


                                      API-compatible unordered_map

                                      Т.е. он вообще противоречит вашим тезисам.


                                      Ресёрч на тему совместимости какой-нибудь там open addressing пополам с cuckoo hashing с unordered_map гуглится чуть сложнее, но их несовместимость — это уровень фольклора (в математическом смысле), не надо писать статьи, чтобы показать, что 2+2 ≠ 5.

                                      2+2 != 5 гуглится просто. К тому же, нужны конкретные предложения и конкретные сравнения. Всё остальное — не говорит о какой-то проблеме в смене api.

                                        +7
                                        Покажите.

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


                                        Т.е. нельзя? Никакая явная типизация не может, потому как она не сможет в такое decltype(a + b)

                                        А зачем это надо?


                                        Вот получили вы тип результата выражения. А дальше-то что вы с ним делаете?


                                        С чего вдруг?

                                        С определения понятия перегрузки функции?


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


                                        Если мне нужно передать имя — я заюзаю лямбду/типа. Точно так же я могу передать его через лямбду.

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


                                        [](auto… args) { return f(args...); } — так.

                                        Неверно.
                                        [](auto&&... args) -> decltype(auto) { return f(std::forward<decltype(args)>(args)...); }.


                                        Написал и прям ощутил мощь системы типов языка.


                                        (auto a, auto b) -> decltype(f(a, b)) перегрузка для случая, когда тип данного выражения выводится. Т.е. функция есть и оно возвращает значение какого-либо типа. Реализуйте тоже самое "не на костылях".

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


                                        decltype здесь — кусок SFINAE, костыль поверх отсутствующего механизма адекватной спецификации требований к типам (и их проверки).


                                        Размахивать костылями «а у вас таких нет» — ну такое.


                                        Оснований нет, хорошо. Зачем о чём-то пытаться рассуждать, если оснований нет?

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


                                        Нет, это не основной документ.

                                        Вау.


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


                                        Никто не измеряет стандарты в компиляторах какими-то С++20. Зачем вы, опять же, рассуждаете о том, в чём не разбираетесь?

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


                                        Никто не сидит и не реализует какой-то там "весь стандарт".

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


                                        И что?

                                        Универсальный ответ. Возьму на вооружение.


                                        json особенно nlohmann — это прикладуха. Какой смысл ссылаться на nlohmann, который вообще не имеет никакого отношения к производительности?

                                        Удобно, ненастоящий шотландец.


                                        Хорошо хоть не скриптуха.


                                        И что? Это документ 2 месячной давности. Очевидно, что никто там за 2 месяца ничего и нигде не изменит.

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


                                        К тому же, этот документ про abi.

                                        Если тяжело поменять ABI, то API тем более тяжело поменять.


                                        Т.е. он вообще противоречит вашим тезисам.

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


                                        Инвертируйте утверждения правильно.

                                          –5
                                          Я ж писал уже — просто пишете инстанс Functor для другого типа, а fmap выбирается уже согласно типу в точке вызова.

                                          Напишите.


                                          А зачем это надо?
                                          Вот получили вы тип результата выражения. А дальше-то что вы с ним делаете?

                                          С ним что-то делает в момент вычисления типа. Я объяснил что выше.


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

                                          Зачем вы продолжаете писать глупости? Вам сообщили про композицию лямбд — там работает перегрузка. Всё.


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

                                          Если мне нужно имя — я пишу структуру/лямбду. Я ни на что не надеюсь.


                                          Неверно.

                                          Верно, а ваша перепаста к теме отношения не имеет. Потому как никакого форварда в хаскеле нет. А значит и в С++ нет. Либо показывайся pf/ссылки и прочее в хаскеле.


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

                                          Зачем вы съезжаете с темы? Я пишу код для того, что-бы у меня была перегрузка. Показывайте на хаскеле аналогичный функционал. Мне нужно при наличии f() — один вызов, а при наличии, допустим, x() — другой.


                                          decltype здесь — кусок SFINAE, костыль поверх отсутствующего механизма адекватной спецификации требований к типам (и их проверки).

                                          Нет, очевидно. Это не кусок sfinae


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

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


                                          А какой основной документ?

                                          А какой основной документ у хаскеля?


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

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


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

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


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

                                          А, ну т.е. всё, аргументация кончилась?


                                          Универсальный ответ. Возьму на вооружение.

                                          Ответ достойный глупостям, что были написаны выше. Какому какое дело до того, что вы там дёргали? Вы выдвигали тезис "везде в С++", а после ссылались на "я". Очевидно, что это позор.


                                          Удобно, ненастоящий шотландец.

                                          Аргументов опять нет? Так и запишем. Или мне бенчмарки доставать? Хотя уже всё забенчено за меня.


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

                                          Пруфы.


                                          Если тяжело поменять ABI, то API тем более тяжело поменять.

                                          Нет, очевидно. Т.е. вы засыпались с примеров, выдавая его не за то, о чём говорили? А теперь уже пошли отговорки вида "да abi/api — какая разница". А чего же так?


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

                                          Нет, он противоречит потому, что говорит о том, что изменения api-совместимые. Вы говорили о других изменениях api-несовместимых. И выдавали его за пруф ваших тезисов — т.е. врали, кидая рандомный документ из гугла.

                          +4
                          Вот плюсы:

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


                          пример явно манипулятивный.

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




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

                            +1
                            Насколько я понимаю, этот синтаксис буквально вот-вот появился.

                            Нет, из нового там только auto, но в данном случае функция тут ненужна — её можно заменить на лямбду добавлением двух символов. auto там уже давно есть.


                            К тому же, это не является проблемой, потому как не является проблемой для js/rust и множества других языков.


                            А пример, который я взял, я взял из книжки 2017 года. Поэтому ваше утверждение что

                            Ну это явно не 17 год, даже если книжка 17 года.


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

                            Манипулятивный не в этом смысле. Это базовый пример из fp-букваря с откусыванием элемента списка. В С++ просто нету таких интерфейсов и не потому, что их нельзя сделать — нет. Просто они не пользуются спросом.


                            Тоже самое и с fmap. Здесь создаётся новый список, что в языка типа С++ не приемлемо, поту как очень медленно. Поэтому там и реализовать transform с такой семантикой — он призван в основном для мутаций, но не только.


                            Допустим, я вынужден был написать to<vector>(), который там не нужен. Так же я вынужден был принять вектор "по значению", что так же не оптимально.


                            Так же, в С++ вообще контейнеры как таковые кого-то мало волнуют. Поэтому и принимаются итераторы.


                            Т.е. данный код целиком и полностью противоречит принятым в С++ подходам. Но он целиком и полностью соответствует паттернам принятым в хаскеле. Он и взят из хаскель-букваря.


                            В этом и заключается манипуляция, на мой взгляд.

                              +3
                              Тоже самое и с fmap. Здесь создаётся новый список, что в языка типа С++ не приемлемо, поту как очень медленно.

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


                              И да, воспринимайте ФП-шный список как такой итератор на стероидах, а не как структуру данных.

                                –1
                                Он создаётся только тогда, когда компилятор не может вывести, что старый не используется.

                                Это просто оптимизация находящая за рамками семантики. Она ни о чём не говорит. К тому же, в С++ нету никаких проблем с тем, что используется. Наоборот — он почти всегда где-то используется. Да и списки никто не использует.


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

                                Только вот в данном коде ничего не рассказывается. А никакие оптимизации на "авось" не полагаются. Предсказуемость — это база любой оптимизации.


                                И да, воспринимайте ФП-шный список как такой итератор на стероидах, а не как структуру данных.

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

                                  +2
                                  Это просто оптимизация находящая за рамками семантики. Она ни о чём не говорит.

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


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

                                  Это типа на race condition'ы намёк? Спасибо, оценил.


                                  Только вот в данном коде ничего не рассказывается. А никакие оптимизации на "авось" не полагаются. Предсказуемость — это база любой оптимизации.

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


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

                                  Примерно потому же, почему я не умножаю матрицы на итераторах.

                                    –2
                                    И не в C++ ее сделать проще, потому что компилятору о коде рассуждать проще.

                                    В С++ её ненужно делать — она уже сделана на уровне семантики.


                                    Это типа на race condition'ы намёк? Спасибо, оценил.

                                    Нет, это намёк на что угодно. Самое интересное тут то, что вы сами выше ссылаетесь на то, что компилятор в кишках заменяет(чисто на уровне тезисов и даже на уровне них — исходя из фазы луны) всё на мутельные структуры данных. А теперь уже это race condition'ы?


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


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

                                    Ещё раз — это ничего не значит. Нет никаких гарантий. К тому же, копия как минимум одна да будет. В случае с С++ её не будет.


                                    Примерно потому же, почему я не умножаю матрицы на итераторах.

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


                                    Почему ваши тезисы расходятся с действиями?

                                      +6
                                      В С++ её ненужно делать — она уже сделана на уровне семантики.

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


                                      Везде, где производительность — везде мутации. Именно поэтому вы идёте и используете массивы.

                                      Да, и?


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


                                      Каждой задаче свой инструмент.


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

                                      Ага. Вы правда не надеетесь, что компилятор развернёт все ваши итераторы и слои шаблонов? На NRVO и copy elision не надеетесь? Не надеетесь, что компилятор соптимизирует memcpy для копирования представления объекта без type punning'а? Если писать на плюсах аккуратно, как нужно, без UB, то плясок и пританцовываний будет куда больше, чем, гм, в среднем коде.


                                      И если вы не надеетесь на компилятор, то собирайте код с -O0, в конце концов. В производительности не потеряете, не так ли?


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


                                      К тому же, копия как минимум одна да будет.

                                      Откуда?


                                      В случае с С++ её не будет.

                                      Какая там нынче актуальная рекомендация для передачи параметров? Pass-by-value и всегда иметь как минимум один мув (привет ммммаксимальной производительности)? Или делать везде perfect forwarding, вынося весь код в хедер? Что, кстати, тоже не гарантирует максимальной производительности, но соответствующий пример будет сконструировать уже чуть сложнее.


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

                                      Потому что, ещё раз, каждой задаче свой инструмент. Я сразу сказал, что список — это управляющая структура, не надо к ней относиться как к структуре данных (и строить соломенные чучела тоже не надо).


                                      Нужно прочитать построчно файл и что-то с ним сделать — делаете unlines <$> readFile path и обрабатываете, лениво и в O(1), прям как с итераторами (только без необходимости писать итераторы и весь код делать зависящим от конкретного типа итератора или выносить в шаблоны).
                                      Нужно дробить числа и что-то хитрое делать — перегоняете список в массив и работаете с ним. Чисто, или нет, или через repa или accelerate какой — вообще десятый разговор.


                                      Выкидываете иммутабельность?

                                      Причём тут иммутабельность-то?

                                        –3
                                        C++ это не специфицирует на уровне семантики. Он требует, чтобы программист не делал ошибок, но никаких гарантий или безопасности сам при этом не даёт

                                        Это лозунги. Конкретика. Очевидно, что язык с лоулевел доступом даёт больше возможностей, но увеличивает вероятность ошибок. Именно поэтому вы идёте в очередных сравнения С++/hs используете unsafe. Почему?


                                        (и не может давать, система типов не позволяет).

                                        Основания предоставьте.


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

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


                                        Ага. Вы правда не надеетесь, что компилятор развернёт все ваши итераторы и слои шаблонов? На NRVO и copy elision не надеетесь? Не надеетесь, что компилятор соптимизирует memcpy для копирования представления объекта без type punning'а? Если писать на плюсах аккуратно, как нужно, без UB, то плясок и пританцовываний будет куда больше, чем, гм, в среднем коде.

                                        Вы запутались в логике. Вы же там сами говорили, что С++ код проще оптимизируется. Ведь компилятору о коде рассуждать проще. Я знаю что развернёт компилятор и развернёт он это всегда. Потому как это примитивные лоулевел оптимизации. rvo для всех(либо почти всех) случаев уже давно требование стандарта.


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


                                        И если вы не надеетесь на компилятор, то собирайте код с -O0, в конце концов. В производительности не потеряете, не так ли?

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


                                        Во-вторых, я не отказываюсь от компилятора. Проблема ваша не в том, что вы надеетесь на компилятор — проблема в том, что вы надеетесь ТОЛЬКО на компилятор. Потому как семантически ваш код не оптимален.


                                        Откуда?

                                        Если я буду использовать старый список одновременно с новым. Семантически в С++ это можно быть один и тот же "список", а в hs нет.


                                        Какая там нынче актуальная рекомендация для передачи параметров? Pass-by-value и всегда иметь как минимум один мув (привет ммммаксимальной производительности)?

                                        Понятия не имею. Мув — это базовая семантика копирования из си. Копирование не является проблемой.


                                        Или делать везде perfect forwarding, вынося весь код в хедер?

                                        pf вообще ненужен, если нету raii. Я не являюсь адептом raii и его у меня почти нигде нет. Поэтому мне вообще без разницы.


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


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

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


                                        Потому что, ещё раз, каждой задаче свой инструмент. Я сразу сказал, что список — это управляющая структура, не надо к ней относиться как к структуре данных (и строить соломенные чучела тоже не надо).

                                        Для списка вообще нет задач. Это почти всегда мёртвая структура данных.


                                        Нужно прочитать построчно файл и что-то с ним сделать — делаете unlines <$> readFile path и обрабатываете, лениво и в O(1), прям как с итераторами (только без необходимости писать итераторы и весь код делать зависящим от конкретного типа итератора или выносить в шаблоны).

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


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

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


                                        Причём тут иммутабельность-то?

                                        При том. Что с неё всё и началось. Вы про что говорили? Что иммутабельная семантика не слабая — компилятор заменит её на мутабельную.


                                        Копирования — это уже следствие этой семантики.

                                          +6
                                          Именно поэтому вы идёте в очередных сравнения С++/hs используете unsafe. Почему?

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


                                          Основания предоставьте.

                                          Теория зависимых типов, зависящих от динамических термов, для императивных языков неизвестна. То есть, вообще, даже в академии, даже на папирах. Топчик, наверное, это ATS, но оно там ближе к стратифицированной системе типов, а не к полноценной зависимой системе типов. Поэтому выразить инвариант «индекс внутри» у вас не получится.


                                          Может, в скалке что-то есть ещё, конечно, но я скалу знаю недостаточно.


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

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


                                          Вы же там сами говорили, что С++ код проще оптимизируется.

                                          Где? Тыкните плиз.


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


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

                                          Основания.


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

                                          И что?


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


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

                                          Ясно.


                                          Проблема ваша не в том, что вы надеетесь на компилятор — проблема в том, что вы надеетесь ТОЛЬКО на компилятор. Потому как семантически ваш код не оптимален.

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


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

                                          У вас не получится это делать. Если вы мутировали список, то старую копию вы потеряли, всё, нет её.


                                          И он в хедерах у любого, кто может и умеет писать на С++

                                          Ну и что, что один TU компилируется 10 минут после малейшего изменения, а clangd жрёт 20 гигов памяти.


                                          потому как иначе нельзя.

                                          А мужики и не знали.


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

                                          И как вы реализуете эти гарантии?


                                          Для списка вообще нет задач. Это почти всегда мёртвая структура данных.

                                          Структура данных — да. Сколько раз мне повторить, что список в ФП — это управляющая структура?


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

                                          И руками находите \n, руками нуль-терминируете строку, чтобы передать её в какое-нибудь легаси (всего-то двухлетнее), которое ждёт std::string, а не std::string_view (которым тоже прикольно стрелять себе в ноги)?


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


                                          Т.е. вы признаете, что fmap несостоятелен?

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


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

                                          Смотря что вы хотите сделать. Записать их потом в файл? Показать на экран? Отправить в сеть? Просуммировать? Ещё как-то свернуть? Во всех этих случаях ничего не надо никуда перегонять или мутировать, можно работать со списком.

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

                                            Ну т.е. у вас ничего нет, никаких оптимизаций нет. Но С++ плохой, потому что там безопасности нет? Это настолько нелепо.


                                            Теория зависимых типов, зависящих от динамических термов, для императивных языков неизвестна. То есть, вообще, даже в академии, даже на папирах. Топчик, наверное, это ATS, но оно там ближе к стратифицированной системе типов, а не к полноценной зависимой системе типов. Поэтому выразить инвариант «индекс внутри» у вас не получится.

                                            Не, это не основания. Это "я не знаю". Основания должны звучать так. "Это невозможно потому-то — вот пруфы". К тому же, зависимых типов в хаскеле нет.


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

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


                                            Основания.

                                            Я написал основания. Основания для игнорирования.


                                            И что?

                                            И то, что понятия "без оптимизаций" в принципе нет. А -O0 к нему никакого отношения не имеет.


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

                                            Не засчитывается.


                                            Ясно.

                                            А теперь полные цитаты.


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

                                            Это какое-то заклинание. Просто перечисление услышанных где-то базвордов. Я могу руками аллоцировать регистры, как и руками инлайнить. Как и могу руками подставлять инструкции — asm часть С/С++.


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


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


                                            У вас не получится это делать. Если вы мутировали список, то старую копию вы потеряли, всё, нет её.

                                            В С++ я ничего не теряю.


                                            Ну и что, что один TU компилируется 10 минут после малейшего изменения

                                            Полная чушь.


                                            а clangd жрёт 20 гигов памяти.

                                            Нет, очевидно. Он её столько не жрёт. А даже если бы жрал — это не проблема. Эта память ничего не стоит.


                                            А мужики и не знали.

                                            Ну теперь знают.


                                            И как вы реализуете эти гарантии?

                                            Руками. Если компилятор собрал код нужным образом — он его соберёт.


                                            Структура данных — да. Сколько раз мне повторить, что список в ФП — это управляющая структура?

                                            А, ну значит вы мне покажите fmap описанный мною ранее.


                                            И руками находите \n, руками нуль-терминируете строку,

                                            Я ничего не нуль-терминирую. Нахожу руками. А могу и не руками.


                                            чтобы передать её в какое-нибудь легаси (всего-то двухлетнее),

                                            Я не пишу прикладуху. А даже если бы писал — это не проблема.


                                            которое ждёт std::string, а не std::string_view (которым тоже прикольно стрелять себе в ноги)?

                                            Ждёт std::string — это прикладу. И это её проблемы. К тому же, всё это без проблем конвертируется автоматически.


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

                                            Опять какие-то лозунги.


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

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


                                            Смотря что вы хотите сделать. Записать их потом в файл? Показать на экран? Отправить в сеть? Просуммировать? Ещё как-то свернуть? Во всех этих случаях ничего не надо никуда перегонять или мутировать, можно работать со списком.

                                            Это неважно. Просто записать в /dev/null. Хорошо, а теперь это нужно проверить. Реализуйте задачу выше на списках, а я на массиве. Сравним.

                                +3
                                Допустим, я вынужден был написать to&ltvector>(), который там не нужен. Так же я вынужден был принять вектор «по значению», что так же не оптимально.
                                ак же, в С++ вообще контейнеры как таковые кого-то мало волнуют. Поэтому и принимаются итераторы.

                                Т.е. данный код целиком и полностью противоречит принятым в С++ подходам. Но он целиком и полностью соответствует паттернам принятым в хаскеле. Он и взят из хаскель-букваря.

                                В этом и заключается манипуляция, на мой взгляд.


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

                                fn fmap<A, B>(f: impl FnMut(A) -> B, xs: impl Iterator<Item=A>) -> 
                                impl Iterator<Item=B> {
                                    xs.map(f)
                                }


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

                                  Какая версия раста нужна что-бы это собрать?


                                  Нужно понимать, что списки — это больше итераторы

                                  Если это итератор и так xs.map(f) можно, то функция реализуется так: auto fmap = transform. Да даже если не итератор — эта реализация так же пойдёт.

                                    +3
                                    Какая версия раста нужна что-бы это собрать?

                                    Конкретно этот — 1.26, когда impl Trait стабилизировали. Но это непринципиально, вот так:


                                    pub fn fmap<A, B, F: FnMut(A) -> B, TA: Iterator<Item=A>>(f: F, xs: TA) 
                                    -> std::iter::Map<TA, F> {
                                        xs.map(f)
                                    }

                                    Соберется на 1.0


                                    Если это итератор и так xs.map(f) можно, то функция реализуется так: auto fmap = transform. Да даже если не итератор — эта реализация так же пойдёт.

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

                                      –4
                                      Конкретно этот — 1.26, когда impl Trait стабилизировали.

                                      Ну в это время гцц уже мог во всех используемые мною фичи.


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

                                      А в С++ ненужно ничего объявлять. В этом суть С++. Вы в расте определяте все эти типы потому, что раст стирает типы. Если вы уберёте аннотации у типов — у вас всё поломается.


                                      В С++ же ненужно ничего аннотировать — он не стирает типы. Обычно людям после генериков подобное поведение непривычно и они пытаются добавлять аннотации там, где в С++ они ненужны.

                                        +4
                                        Ну в это время гцц уже мог во всех используемые мною фичи.

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


                                        А в С++ ненужно ничего объявлять. В этом суть С++. Вы в расте определяте все эти типы потому, что раст стирает типы. Если вы уберёте аннотации у типов — у вас всё поломается.

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


                                        В С++ же ненужно ничего аннотировать — он не стирает типы. Обычно людям после генериков подобное поведение непривычно и они пытаются добавлять аннотации там, где в С++ они ненужны.

                                        Ни шаблоны ни генерики ничего не стирают, вопрос в инстанцировании в момент вызова или в момент объявления. Лично я предпочитаю второе, потому что во-первых ошибки намного понятнее, во-вторых понятно какие требования к типу. Лично мне проще понять constraint T : Foo + Bar is not satisfied чем какой-нибудь couldn't apply operator &>>< to operands "MyDuper" and "MySuper".

                                          –4
                                          Вопрос не в том, когда кто умел, а в том, можно ли это тащить в прод. Если все съездили на конфу, услышали умных чуваков, а потом пошли дальше пилить проект где эти знания ближайшие лет 10 не применить — это такое.

                                          Если на прод затащили раст — затащат и актуальные плюсы. А туда, где легаси — туда и раст не попадёт, как и F#. Как максимум там будет хаскель, но и то крайне сомнительно.


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

                                          В С++ это не просто функция весящая в воздухе. Типы нужны для того, что-бы код валидировался. Если он валидируется без аннотаций — аннотации ненужны. В этом и суть статьи. С++ этому следует как никто другой.


                                          Ни шаблоны ни генерики ничего не стирают

                                          Стираю. Попробуйте запустить эту функцию без аннотаций. Либо получить оригинальный тип. Вы не сделаете ни того ни другому. Потому как тип стёрт внутри функции.


                                          вопрос в инстанцировании в момент вызова или в момент объявления.

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


                                          Лично я предпочитаю второе, потому что во-первых ошибки намного понятнее, во-вторых понятно какие требования к типу.

                                          А если вообще без генериков — ошибки ещё более понятны. А про требования к типу — это какой-то странный тезис. Такое чувство, что вы статью, которую перевели и не читали.


                                          Ведь так и работает хаскель. И автор и говорит, что это хорошо. Мы хотим статически, но не писать тонны церемониальной лапши.


                                          Лично мне проще понять constraint T: Foo + Bar is not satisfied чем какой-нибудь couldn't apply operator &>>< to operands "MyDuper" and "MySuper".

                                          А мне проще читать второе — это не аргумент.


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


                                          К тому же, в С++ уже есть и подход с аннотациями. Который, к тому же, не затирает типы.

                                            +6
                                            Типы нужны для того, что-бы код валидировался. Если он валидируется без аннотаций — аннотации ненужны.

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


                                            Либо получить оригинальный тип. Вы не сделаете ни того ни другому. Потому как тип стёрт внутри функции.

                                            {-# LANGUAGE ScopedTypeVariables #-}
                                            
                                            bottom :: forall a. a
                                            bottom = undefined :: a -- вот он, оригинальный тип

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


                                            readEither' :: forall a. (Typeable a, Read a) => Either String a
                                            readEither' = first (\err -> [i|Error parsing #{str} as #{typeRep (Proxy :: Proxy a)}: #{err}, expected type: #{value cfg}|]) $ readEither str

                                            Или вот (но тут, скорее, Хиндли с Милнером слишком натянули, и у них унификатор порвался, пришлось писать явную аннотацию).
                                            Или вот.


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


                                            А первый вопрос — стирать или не стирать типы. Если стирать типы — можно не инстанцировать для каждого типа и проверять только сигнатуру.

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

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

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


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

                                              Я не вижу там полей. К тому же в С++ ничего ограничивать ненужно. Повторяю — вы что-то там ограничиваете лишь потому, что у вас затираются типы. И вы не можете иначе.


                                              В С++ это ненужно. Единственное для чего в С++ существуют концепты — это для перегрузки, потому как перегрузка "не смотрит" в тело.


                                              А так в С++ было с его рождения. https://godbolt.org/z/As4NVC — по модному так, а по старому — как угодно. Правило простое — от поля должна как-то зависеть сигнатура.


                                              template<typename T, typename = decltype(T{}.name)> void f(T) {}

                                              Конечно, странно задавать такие вопросы используя с++. Все эти вопросы — детский сад для С++. Может какую угодно логику выполнять при перегрузки и про ограничения.


                                              Даже факториал считать. https://godbolt.org/z/SBS4VA — вообще что угодно.


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

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


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

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

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


                                                Ну серьёзно, перестаньте так воевать с другими языками. Попробуйте их. Ваши навыки в C++ от этого не пропадут и не испортятся.


                                                Я не вижу там полей.

                                                Они там в соседней функции, не конкретно в тех строках, на которые я сослался.


                                                К тому же в С++ ничего ограничивать ненужно. Повторяю — вы что-то там ограничиваете лишь потому, что у вас затираются типы. И вы не можете иначе.

                                                Не затираются, я ж вам пример показал. Это даже несерьёзно.


                                                А ограничиваю я потому, что я пишу модульный код. Я хочу выразить констрейнт «функция может работать с любым объектом-конфигом, покуда у него есть поле (с произвольным именем) с заданным типом». Ближайший костыль — ограничиться, скажем, std::tuple и проверять, что std::get<T> — well-formed.


                                                Конечно, странно задавать такие вопросы используя с++. Все эти вопросы — детский сад для С++. Может какую угодно логику выполнять при перегрузки и про ограничения.

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


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


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

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


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

                                                Вот же он — a, во всех случаях. Чем он вас не устраивает?

                                                  –3
                                                  Почему? Кому должен? Кто мешает описать констрейнты заранее, проверить тело функции относительно этих констрейнтов, а потом подставлять конкретные типы, пусть даже безо всяких стираний?

                                                  Потому что нельзя ничего проверить не имя тип. В вашем случае вы никакие типы не проверяете — вы просто аннотируете параметры заново. И уже нету никаких генериков — поэтому и возможна проверка.


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


                                                  Ну серьёзно, перестаньте так воевать с другими языками. Попробуйте их. Ваши навыки в C++ от этого не пропадут и не испортятся.

                                                  Я не воюю с языками.


                                                  Они там в соседней функции, не конкретно в тех строках, на которые я сослался.

                                                  Ну приведите весь контент.


                                                  Не затираются, я ж вам пример показал. Это даже несерьёзно.

                                                  Затираются. Если они не затираются — нужно отдельно тайпчекать каждый вызов.


                                                  А ограничиваю я потому, что я пишу модульный код. Я хочу выразить констрейнт «функция может работать с любым объектом-конфигом, покуда у него есть поле (с произвольным именем) с заданным типом». Ближайший костыль — ограничиться, скажем, std::tuple и проверять, что std::get<T> — well-formed.

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


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


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

                                                  Я не понимаю — зачем? Зачем вы задаёте такие нелепые вопросы. Любой тип проверяется. Любыми проверками. https://godbolt.org/z/CtUg9r


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

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


                                                  Если имён нет, то это тапл и там без проблем что-то ищется. Причём произвольно. К тому же — эта задача не имеет смысла. Зачем мне сувать разные типы в тапл, что-бы потом что-то там искать? Это ваши проблему — у вас нету имён и прочего.


                                                  У меня есть имена. А если прям мне нужно по типу что-то делать — я могу написать это руками без проблем. Повторю — С++ на уровне типов дважды тюринг-полный.


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

                                                  Нет, у вас нет типов — у вас есть аннотации. Это и есть типы. Те типы, которые вы передаёте — в тайпчеке не участвуют. Если бы участвовали — нужно было бы тайпчекать каждый инстанс, как это делает С++.


                                                  Если проще у вас типизация как жава. f(base_class arg) и далее вы тайпчекаете с arg как с этим типом. А уже то, что туда передаёте тип g — вы не чекаете. Он просто затирается, как в жаве.


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


                                                  Вот же он — a, во всех случаях. Чем он вас не устраивает?

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

                                                    –1

                                                    Вот с поиском типа в тапле. А вообще меня удивляют эти вопрос, повторю. Какие-то крайне наивные представления о С++, фанатизм и прочее.


                                                    template<typename Ttuple, typename T> concept with_type = requires(Ttuple tuple) {
                                                      to_set(transform(tuple, decltype_))[type_c<T>];//я могу тут любую логику писать
                                                    };
                                                    
                                                    void f(with_type<int> auto) {}
                                                    
                                                    int main() {
                                                      f(std::tuple{10});
                                                    }

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


                                                    Поэтому вопрос уровня "может ли С++ что-то там найти в типах" — несостоятелен. Скорее что-то иное чего-то не найдёт, но не С++.


                                                    Единственное чего нет в С++ — это интроспекции и рефлексии. Т.е. получить список полей структуры, либо ещё что-то — нельзя. Так же нельзя сгенерировать функции или прочее.


                                                    Но реализация рефлексии уже есть. Плюс есть clang — прикрутить рефлексию уровня "как в расте" — можно за пол дня. Да что угодно можно.


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


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


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

                                                      +2
                                                      Поэтому вопрос уровня "может ли С++ что-то там найти в типах" — несостоятелен.

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


                                                      Единственное чего нет в С++ — это интроспекции и рефлексии. Т.е. получить список полей структуры, либо ещё что-то — нельзя. Так же нельзя сгенерировать функции или прочее.

                                                      Ну вы сами и ответили, тащем. Непонятно, зачем было писать предыдущий тезис?


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

                                                      Есть. От TH и Generics в хаскеле (тут они подойдут лучше) до всяких растовских макросов.


                                                      Так и здесь. "у С++ слабая система типов, а вот у хаскеля".

                                                      Напишите тип в C++, который запрещает мутацию глобального состояния. Или который запрещает вещи, которые нельзя делать в STM-транзакции.


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

                                                      Я и сразу говорю, что их там нету. Зачем вы врёте?

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

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


                                                        Ну вы сами и ответили, тащем.

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


                                                        Непонятно, зачем было писать предыдущий тезис?

                                                        Там всё объясняно.


                                                        Есть. От TH и Generics в хаскеле (тут они подойдут лучше) до всяких растовских макросов.

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


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


                                                        Хотя зачем я что-то объясняю — пусть позорник сам пруфцует всю эту чушь нелепую.

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

                                                          И много Вы видели "вживую" плагинов для компилятора в Rust? Я так навскидку могу назвать разве что стандартные вещи типа clippy, и то с натяжкой (потому что это отдельная программа, которая использует компилятор как обычную библиотеку). Или Вам просто не нравится название "процедурные макросы"?

                                                            –3
                                                            И много Вы видели "вживую" плагинов для компилятора в Rust?

                                                            Плагин это не только:


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

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


                                                            Или Вам просто не нравится название "процедурные макросы"?

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

                                                              +3

                                                              А можно было сразу прямо сказать, что Вы не согласны с официально принятой в соответствующей среде терминологией (и поэтому procedural macros в Rust — это для Вас не макросы)? Ну и вообще в аналогичных случаях сразу раскрывать свою мысль. Меньше же потом объяснять придётся.

                                                                –6
                                                                А можно было сразу прямо сказать, что Вы не согласны с официально принятой в соответствующей среде терминологией (и поэтому procedural macros в Rust — это для Вас не макросы)?

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


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


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


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


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

                                                                Читать нужно — "я сел в лужу и попытался нелепо оправдаться", типа "я сразу не понял, что ты дурак, но ты дурак".

                                                                  +5
                                                                  1. Так как в общепринятом смысле слова "адептом" чего бы то ни было я не являюсь, а используемое Вами значение слова мне неизвестно, первое высказывание для меня не имеет смысла. Более того, с большой долей вероятности оно не имеет смысла также и для остальных присутствующих в этом обсуждении, кроме Вас.


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


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


                                                                    –4
                                                                    Так как в общепринятом смысле слова "адептом" чего бы то ни было я не являюсь

                                                                    Любой адепт уверяет всех, что он не адепт. Это стоит ноль.


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

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


                                                                    Если Вас не волнует, как называется какое-то явление в той среде, в которой оно существует, не стоит доказывать, что оно называется именно так, как Вы сказали, а не иначе ("это не макрос, а плагин компилятора").

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


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


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

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


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

                                                                    Не нужно.


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

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


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

                                                                    Для этого ненужно паранормальных способностей.

                                                                      +5

                                                                      Начну с конца:


                                                                      Для этого ненужно паранормальных способностей.

                                                                      Палитесь, товарищ. Впрочем, я сделаю вид, что по-прежнему считаю Вас человеком, но всё же — палитесь.


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


                                                                      Любой адепт уверяет всех, что он не адепт. Это стоит ноль.

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


                                                                      На самом деле у них одна методичка на всех, они все из одной секты.

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


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

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


                                                                      Поэтому, я могу это называть как хочу.

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


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

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

                                                                        –1

                                                                        Отвечу сразу на тему плагина


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


                                                                        Вначале это были просто всякие хинты в комментах. Но хинты на то и хинты, что они чисто декларативны. Это мета-аннотации.


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


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


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


                                                                        Т.е. это код, код на внешнем языка, который исполняет компилятор. Это расширение логики компилятора. И семантически и с ТЗ реализации.


                                                                        В расте объектная модель примитивная как топор. Там есть только структуры как в си(из llvm). Больше ничего нет. Нет методов, нет областей видимости, наследования и всего того, что и делает интроспекцию сложной.


                                                                        https://godbolt.org/z/BMGSSU — это, конечно, вещь несоизмеримо более мощная. Но пример и подход тот же. Плагины писать сложно — поэтому реализуется dsl для удобной го написания правил. Но по-сути это расширение.


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


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


                                                                        Да и, как я уже говорил, существует реализация рефлексии/интроспекции для С++. Реализовать там поиск поиск поля с нужным типом достаточно просто: https://godbolt.org/z/nGjMro


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

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

                                                                          Ух ты, и получили то (нифига не стандартизованное, правда), что было в никому не нужном скриптушном хаскеле уже лет 17 (template haskell — ровно про это, чтобы обычными функциями оперировать представлением исходников).


                                                                          Кстати, как насчёт того, чтобы с вашей макрухой считать файл и на базе него сгенерировать какие-то данные или типы? Смогёте?

                                                                            –2
                                                                            Ух ты, и получили то (нифига не стандартизованное, правда)

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


                                                                            что было в никому не нужном скриптушном хаскеле уже лет 17 (template haskell — ровно про это, чтобы обычными функциями оперировать представлением исходников).

                                                                            Подождите, но ведь оно тоже не стандартное. И что, поломалось методичка?


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


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


                                                                            Кстати, как насчёт того, чтобы с вашей макрухой считать файл и на базе него сгенерировать какие-то данные или типы? Смогёте?

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


                                                                            Во-вторых — файлы это уровень скриптухи. В не скриптухи существует только код. Всё код. С файлами в пхп.


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


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

                                                                              +4
                                                                              Подождите, но ведь оно тоже не стандартное.

                                                                              Это совершенно неважно.


                                                                              К тому же, это полная чушь. Ничего этого в этой скриптухи нет. Адепт врёт.

                                                                              Опять «вывсёврёти». Ну вы бы хоть погуглили бы прежде чем так позориться.


                                                                              А колхоз-интроспекция уровня начальной школы

                                                                              Которую до сих пор даже в C++ не завезли, ага.


                                                                              eval — этому уже лет 30 в скриптухи.

                                                                              Нет, eval там нет.


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

                                                                              То есть, код с вас, а отвечать должен за это я?


                                                                              Во-вторых — файлы это уровень скриптухи. В не скриптухи существует только код. Всё код. С файлами в пхп.

                                                                              Это уже совсем трэш пошёл. Вот хочу я считать XML с описанием схемы сообщений, или HTML с ещё чем-то, да мало ли, что мне в C++ делать?

                                                                                –2
                                                                                Это совершенно неважно.

                                                                                Если это неважно, зачем адепт об этом пытается сообщить?


                                                                                Опять «вывсёврёти». Ну вы бы хоть погуглили бы прежде чем так позориться.

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


                                                                                Задача показана — задача адепта воспроизвести, а не болтать.


                                                                                Которую до сих пор даже в C++ не завезли, ага.

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


                                                                                То есть, код с вас, а отвечать должен за это я?

                                                                                Какой код? Отвечать нужно за болтовню. Я дал задачу на демонстрацию диспатча и инлайна. Ваша задача воспроизвести. Не сможете — ты сели в лужу.


                                                                                Это уже совсем трэш пошёл. Вот хочу я считать XML с описанием схемы сообщений, или HTML с ещё чем-то, да мало ли, что мне в C++ делать?

                                                                                Я сразу сказал, что адепт начнёт эту историю про скриптуху. Никакой хмл, хтмл и прочие скриптушное нечто — ненужно.


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


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


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

                                                                                  +2
                                                                                  Если это неважно, зачем адепт об этом пытается сообщить?

                                                                                  Потому что в контексте плюсов это важно.


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

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


                                                                                  Вернее, конечно, получится, но эффект будет не тот, что ожидается.


                                                                                  Они не соответствует тем требованиям, что предъявляются тут.

                                                                                  А какие требования предъявляются тут?


                                                                                  Я дал задачу на демонстрацию диспатча и инлайна.

                                                                                  Которая не имеет никакого отношения к обсуждаемому предмету.


                                                                                  Ваша задача воспроизвести.

                                                                                  Я воспроизвёл. Одним из наиболее удобных вариантов (т. к. там UB).


                                                                                  Никакой хмл, хтмл и прочие скриптушное нечто — ненужно.

                                                                                  Напоминает фанатов Go, у которых и дженерики-темплейты ненужно.


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

                                                                                  Да, кодогенерацией.

                                                                                    –1
                                                                                    Потому что в контексте плюсов это важно.

                                                                                    Нет.


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

                                                                                    Нет, адепт не дал никакой ссылки. Адепт просто болтает. Всё, что болтает и на что нет пруфов и обоснований(хоть каких-то) — балабольство. Никто не просит болтуна идти и в стандарт — он всё ровно ничего не знает и не осилит его прочитать.


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


                                                                                    А какие требования предъявляются тут?

                                                                                    Такие, почему адепт ваяет примитивное легаси-говно на С++, а не бороздит космос на хаскеле.


                                                                                    Которая не имеет никакого отношения к обсуждаемому предмету.

                                                                                    Имеет. Основания. Я их предоставил. Болтун нет.


                                                                                    Я воспроизвёл. Одним из наиболее удобных вариантов (т. к. там UB).

                                                                                    Во-первых адепт не доказал наличия там УБ и его нет. Во-вторых УБ ничего не значит и никого не интересует и никак болтуна от ответа не освобождает.


                                                                                    К тому же, болтун даже тут сел в лужу. Нельзя УБ как-то трактовать. Опять болтун где-то услышал какую-то чушь и повторяет.


                                                                                    Напоминает фанатов Go, у которых и дженерики-темплейты ненужно.

                                                                                    Чем же?


                                                                                    Да, кодогенерацией.

                                                                                    Нет. Опять позор нелепый.

                                                                                      +1
                                                                                      Нет.

                                                                                      Основания.


                                                                                      Нет, адепт не дал никакой ссылки.

                                                                                      Ссылка была выше. Если она вам не нравится, то вопросы не к ней, а к вам.


                                                                                      Привести хоть какие-то доводы, а не бла-бла я так сказал.

                                                                                      Ну так начните приводить хоть какие-то доводы, кроме копипасты из туториала по хане, которую вы умудрились сломать.


                                                                                      Такие, почему адепт ваяет примитивное легаси-говно на С++, а не бороздит космос на хаскеле.

                                                                                      То есть, вы согласны с тем, что не знаете C++? Отлично, наконец-то.


                                                                                      Имеет.

                                                                                      Основания.


                                                                                      Во-первых адепт не доказал наличия там УБ и его нет.

                                                                                      Аналогично: если вам не нравятся доказательства, то вопрос не к доказательствам, а к вам.


                                                                                      Во-вторых УБ ничего не значит и никого не интересует

                                                                                      Основания.


                                                                                      Всех интересует ведь.


                                                                                      Чем же?

                                                                                      Вот этим всем.

                                                                                        0
                                                                                        Основания.

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


                                                                                        Ссылка была выше. Если она вам не нравится, то вопросы не к ней, а к вам.

                                                                                        Ещё раз ссылку, к тому же там говорилось не только о ссылке.


                                                                                        То есть, вы согласны с тем, что не знаете C++? Отлично, наконец-то.

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


                                                                                        Тут явные проблемы с логикой и методичкой.


                                                                                        Аналогично: если вам не нравятся доказательства, то вопрос не к доказательствам, а к вам.

                                                                                        УБ нет. Аналогично: если вам не нравятся доказательства, то вопрос не к доказательствам, а к вам.


                                                                                        Основания.

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


                                                                                        Вот этим всем.

                                                                                        Т.е. ничем?

                                                                                          +2
                                                                                          Я не обязан доказывать отсутствие чего-то — это задача адепта доказывать присутствие. В данном случае важности.

                                                                                          Доказательство было выше.


                                                                                          С чего вдруг?

                                                                                          С этого треда. Ваше незнание зафиксировано 7 раз на этой странице.


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

                                                                                          А, прошу прощенья, товарищ начальник! Не признал вас!


                                                                                          УБ нет. Аналогично: если вам не нравятся доказательства, то вопрос не к доказательствам, а к вам.

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


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

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


                                                                                          Т.е. ничем?

                                                                                          Адепт поплыл и пытается сыграть в клоуна.


                                                                                          Скрытый текст

                                                                                          Чёрт, мне это даже начинает нравиться.

                                                    +1

                                                    Во первых у вас (множественное число) в комментах смешался хаскель и раст. Как раз судя по статье в хаскеле не обязательно (поправьте если что я хаскель не знаю) указывать ограничение на типы аргументов функций и он может вывести их в некоторых случаях сам, а в раст обязательно. Допустим в статье хаскель понял что аргумент должен быть Foldable + Ord + Num.


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

                                                      0
                                                      Во вторых для завтипов есть вопрос, что все ограничения наверное будет сложно перечислять явно

                                                      Ограничения вроде Foldable или Num там на практике не сильно сложнее.


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

                                                      Иметь неплохо, но можно формально показать, что это неразрешимая задача.

                                                        0

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

                                                          +2

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

                                                            0

                                                            Ну я пишу в определении внешней функции тип B или тип C, или их общий тип А, а то из дискуссий не понятно как она работает, буду рад для общей картины чуть подробнее увидеть пример.

                                                              +3

                                                              Вы пишете что-то вроде


                                                              class HasFooFunc a where
                                                                foo :: smth -> a -> smthElse
                                                              
                                                              instance HasFooFunc B where
                                                                foo = ...
                                                              
                                                              instance HasFooFunc C where
                                                                foo = ...
                                                              
                                                              outerFunc :: HasFooFunc h => h -> ...
                                                              outerFunc someH = ... тут зовёте foo на someH ...

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

                                                                0

                                                                А как работают orphan rules/специализации? В расте с этим знаю есть определенные сложности.

                                                                  0

                                                                  Компилятор выдаёт ворнинг (который можно сделать error'ом).


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

                                                                    0

                                                                    А если это const generics? в расте к сожалению перегрузка по ним не работает.

                                                                      0

                                                                      В хаскеле нет const (вернее нет не const), поэтому не думаю, что вопрос можно вот прям так перенести.

                                                                        0

                                                                        Т.е. перегрузка не только по типу но и по какому-то значению типа factorial(3) сработает?

                                                                          +1

                                                                          В расте ведь точно так же реализовано. into() и parse() например по результирующему типу подхватывают нужную функцию.

                                                                            0

                                                                            А при чем тут это? Если я имел ввиду константные темплейт аргументы fn f<{3}>() { ... }

                                                                            0

                                                                            Я не понял вопрос (вернее, не смог связать его с контекстом).

                                                                              0

                                                                              Например могу ли я сделать для аргумента равного 3 одну реализацию а для равного 10 другую если аргумент известен в компайл тайм и является результатом вычисления других функций? Вот это был вопрос.


                                                                              Upd; даже не так, а еще для остальных n определить рекурсивно через 3 и 10 (например).

                                                                                +3

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


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

                                                                                  0
                                                                                  Но конкретно в хаскеле у вас (пока что) будет отдельная функция для рантайм-вычислений, и отдельная — для компилтайм (вернее, для вычислений во время тайпчекинга)

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


                                                                                  Вообщем-то интересно разобратся как это все формализовать иначе какие-то одни обрывки.


                                                                                  Ну и собственно есть ли альтернатива тому как это сделано в С++ когда все разбирается по ходу дела?


                                                                                  Например с одной стороны мне нужно знать одно и то же Foo<f(10)> и Foo<g(3)> — я могу пойти вычислять это всё, но с другой стороны чтобы вычислить мне надо про тайпчекнуть f и g, а если внутри f или g есть какой-то Foo<?>, как избегать таких ситуаций?


                                                                                  Ну а с другой стороны если допустим я перенесу это на другой уровень и скажу ладно компайл тайм функции пишем через какие то стремные типы как сделано в typenum в расте через например тупли вида (1, (0, (1, 0))) = 10. Что это будет значить для этой надстройки над языком, будут ли там такие же такие гарантии как и в самом языке или мы в некотором смысле обходим тайпчекинг?

                                                                                    0
                                                                                    Или пример навеянный докладом Александреску, у нас есть какие то структуры мы знаем их размер или другие характеристики далее хотим в зависимости от этого подогнать параметры quick sort т.е. сделать перегрузку функции сортировки.

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


                                                                                    Ну а с другой стороны если допустим я перенесу это на другой уровень и скажу ладно компайл тайм функции пишем через какие то стремные типы как сделано в typenum в расте через например тупли вида (1, (0, (1, 0))) = 10.

                                                                                    Даже не обязательно такие тупли, у нас завезли, например, полноценные натуральные числа с нормальными операциями на уровне типов. Но функции чуть сложнее надо писать через type families всё равно.


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

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

                                                                                      0

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

                                                                                        0

                                                                                        А, ну это сделать куда проще, так как эта информация-то доступна статически.

                                                                                      0

                                                                                      С вашего позволения продолжу вопрос про перегрузки. Для конкретного типа (например Foo<int>, Foo<float>) понятно как делать специализацию потому что есть однозначность. Но в том что я имел ввиду про Foo<f(10)> и Foo<g(3)> — тут например конкретный тип не известен пока не вычислим f(10) и g(3). Или например перегрузка по неким трейтам ака enable_if. Я вижу проблему в том что как узнать какая имплементация более узкая а какая более широкая, как узнать могут ли эти множетсва одного instance и другого instance перекрываться. Как это проблема решается?


                                                                                      Upd. поправил код который съел хабр

                                                                    –1

                                                                    Простая задача, самый базовый вариант https://godbolt.org/z/5F5mt9 — повторите.

                                                                      +6

                                                                      Сорь, у вас там UB, поэтому эта программа может делать всё, что угодно, включая ничего. Так что я выбираю «ничего».

                                                                        –5
                                                                        Сорь, у вас там UB, поэтому эта программа может делать всё, что угодно, включая ничего.

                                                                        Где? Почему лозунги я слышу, а оснований нет?


                                                                        Так что я выбираю «ничего».

                                                                        Читаю как "я не могу даже распарсить это, не что повторить". Я правильно прочитал?

                                                                          +6
                                                                          Где? Почему лозунги я слышу, а оснований нет?

                                                                          Никаких лозунгов, констатация факта.


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


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


                                                                          Читаю как "я не могу даже распарсить это, не что повторить". Я правильно прочитал?

                                                                          Основания.

                                                                            +6
                                                                            Если вдруг кому-то ещё будет интересно, я с радостью напишу, но до той поры — попробуйте сами.

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

                                                                              +5

                                                                              Давайте рассмотрим, какой тип лежит по указателям in/out на самом деле.


                                                                              1. Это uint16_t. Тогда сдвигать in на один байт на строке 14 нельзя: он либо до этого, либо после этого будет указывать не на объект типа uint16_t, а в его середину. Разыменовывать такой указатель нельзя.
                                                                              2. Это не uint16_t. Тогда тут уже всеми любимый алиасинг.

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

                                                                                +2

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


                                                                                upd: перечитал — кажется, дошло: по идее, если там на самом деле char — мы не можем его читать как uint16_t (но можно было бы наоборот).

                                                                                  +2
                                                                                  В этом случае тоже алиасинг помешает? char * же, насколько я помню, алиасить как раз можно с чем угодно?

                                                                                  Upd правильный, но все же напишу. char, signed char и unsigned char — три разных типа, и алиасить может только первый.


                                                                                  по идее, если там на самом деле char — мы не можем его читать как uint16_t (но можно было бы наоборот).

                                                                                  Абсолютно верно.

                                                                                    –4
                                                                                    правильный, но все же напишу. char, signed char и unsigned char — три разных типа, и алиасить может только первый.


                                                                                    Сходил тут недавно в цирк. Что-то меня накрыли флешбеки с клоунам, не знаю почему. Вроде тут их нет. А вдруг?

                                                                                    If a program attempts to access the stored value of an object through a glvalue of other than one of the
                                                                                    following types the behavior is undefined:58
                                                                                    (11.1)
                                                                                    — the dynamic type of the object,
                                                                                    (11.2)
                                                                                    — a cv-qualified version of the dynamic type of the object,
                                                                                    (11.3)
                                                                                    — a type similar (as defined in 7.3.5) to the dynamic type of the object,
                                                                                    (11.4)
                                                                                    — a type that is the signed or unsigned type corresponding to the dynamic type of the object,
                                                                                    (11.5)
                                                                                    — a type that is the signed or unsigned type corresponding to a cv-qualified version of the dynamic type
                                                                                    of the object,
                                                                                    (11.6)
                                                                                    — an aggregate or union type that includes one of the aforementioned types among its elements or non-
                                                                                    static data members (including, recursively, an element or non-static data member of a subaggregate or
                                                                                    contained union),
                                                                                    (11.7)
                                                                                    — a type that is a (possibly cv-qualified) base class type of the dynamic type of the object,
                                                                                    (11.8)
                                                                                    — a char, unsigned char, or std::byte type.


                                                                                    — a char, unsigned char, or std::byte type.

                                                                                    Ой, поломалось что-то? Ну бывает.

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

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

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

                                                                                        –1

                                                                                        Ну я и не просил оправданий — я просил меньше веры (возможно?)братьям по вере.


                                                                                        А так я нашел терминальный пруф: enum class byte : unsigned char {}; На этом можно закончить с историей. Я и забыл про него. В целом и люди, которые работают с байтами и люди, которые пишут стандарт — знают как байты должны выглядеть. Наши мнения сошлись.

                                                                                          +1
                                                                                          А так я нашел терминальный пруф: enum class byte: unsigned char {};

                                                                                          Терминальный пруф чего?


                                                                                          enum class U : T порождает тип, отличный от T, к слову.

                                                                                        +1

                                                                                        А на вторую часть (по идее, если там на самом деле char — мы не можем его читать как uint16_t (но можно было бы наоборот)) ответ будет?

                                                                                          –4

                                                                                          С чего он должен быть? Это задача балабола — пруфцевать свои истории, а не мои. Балабол уже засыпался и опозорился.

                                                                                          +4

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


                                                                                          Впрочем, если в комментарий с описанием причин UB подставить «char или uchar» вместо «char», то ничего не изменится.

                                                                                            –5
                                                                                            Спасибо, что поправили (хотя удивлён, что вы сослались на стандарт — на него же всем плевать, включая вас).

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


                                                                                            А так, очевидно, мне на него насрать. А всякие клоуны ссылаются на стандарт только потому, что 1) ничего о нём не знают и надеяться на то, что никто там ничего искать не будет. И обычно никто не ищет, да и я бы не пошел искать. 2) ничего более ответить не могут.


                                                                                            Я дал задание — адепт не смог. Начались мазы. Адепт даже с мазами сел в лужу. Ничего нового.


                                                                                            Я не умею разрешимо пруфать наличие UB кодом, извините.

                                                                                            Пруфцевать нужно кодом не УБ, а балабольство про инлайнинг и разницы нет.


                                                                                            А УБ нужно пруфцевать стандартом и обоснованием, каким образом данный пример соотносится с процитированным(которого нет).


                                                                                            enum class U: T порождает тип, отличный от T, к слову.

                                                                                            Нет, в целом даже эти нелепые потуги не работают. Потому как стандарт явно декларирует эквивалетность std::byte и unsigned char.


                                                                                            К тому же, опять адепт позорится. Тип и тип данных — разные вещи. Опять же рассуждения того, кто ничего не понимает ни в матчасти, ни к крестах.


                                                                                            Ну и что-бы окончательно похоронить эту клоунаду:


                                                                                            For an enumeration whose underlying type is fixed, the values of the enumeration are the values of the
                                                                                            underlying type

                                                                                            Значение енума с типом unsigned char являются значением типа unsigned char по стандарту.

                                                                                              +1
                                                                                              Пруфцевать нужно кодом не УБ, а балабольство про инлайнинг и разницы нет.

                                                                                              Вы, похоже, запутались, что вам нужно пруфцевать.


                                                                                              Потому как стандарт явно декларирует эквивалетность std::byte и unsigned char.

                                                                                              #include <iostream>
                                                                                              #include <type_traits>
                                                                                              
                                                                                              int main()
                                                                                              {
                                                                                                  std::cout << std::is_same_v<std::byte, unsigned char> << std::endl;
                                                                                              }

                                                                                              Что выведет эта программа?

                                                                                                –1
                                                                                                Вы, похоже, запутались, что вам нужно пруфцевать.

                                                                                                С чего вдруг?


                                                                                                Что выведет эта программа?

                                                                                                Зачем вы позоритесь? Я дал цитату из стандарта, где написано тоже, что говорю. А то, что вы нагуглили is_same — ничего не значит. Это разные категории типов. Советую почитать букварь по С++.


                                                                                                const int и int — тоже разные типы, но тип данных один. Тоже самое с, допустим, volatile.

                                                                                                  0
                                                                                                  Я дал цитату из стандарта, где написано тоже, что говорю.

                                                                                                  То, что я процитировал из вашего сообщения, отличается по смыслу от того, что написано в стандарте.

                                                                                                    –2
                                                                                                    То, что я процитировал из вашего сообщения, отличается по смыслу от того, что написано в стандарте.

                                                                                                    Я жду основания этой чуши. Кстати, чего же балабол забыл про is_same и позор с ним? Что там с методичкой? Опять засыпался? Я не вижу оправданий на unsigned char?

                                                                                                      0
                                                                                                      Кстати, чего же балабол забыл про is_same и позор с ним?

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

                                                                                                        –1

                                                                                                        Ну дак где же? Опять балабольство? Я могу позор показать и не один. Где мой-то позор? Опять методичка поломалась?

                                                                                                          0

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


                                                                                                          Это если о языке и ваших «познаниях» в нём говорить.

                                                                                          0

                                                                                          А вот это странно, что aliasing не симметричный. Т.е. что угодно можно читать как char* но char* нельзя читать как что то другое. Скорее всего так сделано потому что не только алиасинг но еще и выравнивание. Хотя выравнивание зависит от платформы так что это неким общим документом боюсь не опишешь. Иначе у меня вопрос как писать SSE инструкции? Это скорее всего исключения типа char*.

                                                                                            +1
                                                                                            Т.е. что угодно можно читать как char но char нельзя читать как что то другое.

                                                                                            Кстати, если это формулировать вот прям так, то понятно, почему этого нет: потому что тогда что угодно вы можете читать как что угодно (сначала читаете как char*, а потом как другое что угодно).

                                                                                              0

                                                                                              Нет отрицание этого выражение это то что char* можно читать как что то другое (но не что угодно)

                                                                                                0

                                                                                                А, ну если там квантор существования, то тогда да.

                                                                                                  0

                                                                                                  Ну да потому что если в том буфере char* на самом деле лежит T то можно делать reinterpert_cast к T* и дальше работать с ним. т.е. ваш первый пункт из сообщения про UB, в документации без поллитра не разберешься.

                                                              +4
                                                              Стираю. Попробуйте запустить эту функцию без аннотаций. Либо получить оригинальный тип. Вы не сделаете ни того ни другому. Потому как тип стёрт внутри функции.

                                                              Пожалуйста, используйте термины по назначению.


                                                              Стирание типов — это то, что происходит с параметрами дженериков в Java и с любыми типами в Typescript. А то, о чём пишете вы, называется как-то по-другому.

                                                              –1

                                                              Давайте отделять мух от котлет т.е. прод от технологий и брать последний релиз компилятора за общий знаменатель.

                                                  +1
                                                  auto fmap(auto f, auto v) {
                                                  return v | transform(f) | to<std::vector>();
                                                  }

                                                  Так и все же… какой тип-то будет у этой ф-ции? Что-то отличное от: any -> any -> any или этим все и ограничивается?
                                                    –2
                                                    Так и все же… какой тип-то будет у этой ф-ции? Что-то отличное от: any -> any -> any или этим все и ограничивается?

                                                    У этой функции нет типа. Это множество функции, которые существуют только для типов удовлетворяющих её телу. Её не описать такими any -> any -> any примитивными сигнатурами. Этого недостаточно для выражения системы типов С++.


                                                    Каждая функция из множества сохраняет типы переданных в неё параметров(там есть кое какие нюансы, но это издержки лоулевел работы с памятью(и не только). Чего в том же хаскеле/f# и прочем — нет).


                                                    Допустим, в случае в случае https://godbolt.org/z/MAGPqV


                                                    Тип будет примерно следующим:


                                                    auto fmap(f:auto, v:auto) [
                                                    f:auto = boost::hana::placeholder_detail::plus_right<int>,
                                                    v:auto = ranges::experimental::shared_view<std::vector<int, std::allocator<int>>>
                                                    ]

                                                    Возвращаемое значение там не написано, но оно будет std::vector.

                                                    Это можно проверить так: https://godbolt.org/z/EnfTRJ


                                                    Если убрать конвертацию в вектор — будет так: https://godbolt.org/z/Bj9wca


                                                    void f(auto:16) [with auto:16 = ranges::transform_view<ranges::experimental::shared_view<std::vector<int> >, boost::hana::placeholder_detail::plus_right<int> >]
                                                    

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


                                                    Каждый тип — это отдельная программа с уникальной логикой.

                                                      +3
                                                      У этой функции нет типа. Это множество функции, которые существуют только для типов удовлетворяющих её телу. Её не описать такими any -> any -> any примитивными сигнатурами. Этого недостаточно для выражения системы типов С++.
                                                      :-))) Честно говоря, я даже не знаю, что вам на это ответить. Вы просто не понимаете, о чем рассуждаете, или это просто такой тонкий троллинг? Не надо так…

                                                      Предположу, что вы просто не знакомы со «стрелочной нотацией». Так вот… «примитивный» тип any -> any -> any — ко всему прочему — включает в себя и любую ф-цию двух аргументов — внезапно, это тоже «множество» :-)

                                                      И тип этой fmap — в любом случае — можно описать как any -> any -> any. Вопрос в том, можно ли его описать, скажем так, чуть строже? :-)

                                                      P.S. Яж правильно понимаю, что «удовлетворяют её телу» наши аргументы или нет, можно узнать только на этапе компиляции?
                                                  0
                                                  Правильно ли я понимаю, что приведенный пример — это применение некой функции f к каждому элементу списка и возврат нового списка в результате?

                                                  Если я правильно понял, в той же многословной java это будет:
                                                  class Fmap {
                                                      static <A, B extends A> Collection<B> fmap(Function<A, B> f, Collection<A> a) {
                                                          return a.stream().map(f).collect(toList());
                                                      }
                                                  }
                                                  

                                                  и из церемоний тут:
                                                  1. описание явной сигнатуры метода (да, громоздко)
                                                  2. обернуть все в класс и объявить как static (т.к. функций первого порядка нету, но есть статик импорты)

                                                  Без сомнения, у хаскеля система типов мощнее и кода пишется меньше. Но сложность этого кода для понимания на порядок выше. Учитывая, что программист тратит 60-80% времени на чтение кода, в случае со столь сложной системой типов необходимо приложить колоссальные усилия, чтобы разобрать пусть небольшой но невероятно сложный код. С точки зрения поддержки и командной работы над проектом – вся команда должна будет состоять из очень одаренных людей. Увы, таких людей очень мало.

                                                  Кстати, та же декларация на TypeScript:
                                                  const fmap = <A, B extends A>(f: (A) => B, a: Array<A>): Array<B> => a.map(f);
                                                  


                                                  И на JS:
                                                  const fmap = (f, a) => a.map(f);
                                                  
                                                    +1

                                                    Верно. Правда, в данном конкретном случае вы написали конкретно для списка, а код выше работает для любых маппящихся вещей (см. F<A> вместо Collection<A>).


                                                    Кстати я не уверен, что часть с B extends A — верна.


                                                    Без сомнения, у хаскеля система типов мощнее и кода пишется меньше. Но сложность этого кода для понимания на порядок выше. Учитывая, что программист тратит 60-80% времени на чтение кода, в случае со столь сложной системой типов необходимо приложить колоссальные усилия, чтобы разобрать пусть небольшой но невероятно сложный код. С точки зрения поддержки и командной работы над проектом – вся команда должна будет состоять из очень одаренных людей. Увы, таких людей очень мало.

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


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


                                                    const fmap = <A, B extends A>(f: (A) => B, a: Array<A>): Array<B> 
                                                      => 1000 строк нечитаемого кода

                                                    Могу понять намного больше, чем из


                                                    const fmap = (f, a) 
                                                      => 1000 строк нечитаемого кода
                                                      0
                                                      Могу понять намного больше, чем из

                                                      Что врядли сильно сигнатура поможет понять, что делается в «1000 строк нечитаемого кода». Ладно бы 10, 100, но в 1000 строк наверняка попутно делается намного больше из заявленного сигнатурой. Т.е. если Вы напишете свою реализацию в 10 строк, то наверняка она не будет обладать тем же поведением, что и 1000 строк (всякие побочные действия)
                                                      Ибо 1000 строк простыни — это почти всегда очень дурно.
                                                        +2
                                                        Что врядли сильно сигнатура поможет понять, что делается в «1000 строк нечитаемого кода».

                                                        В хороших системах типов — кроме сигнатуры больше ничего не надо. Собственно это товарищ 0xd34df00d и показывал в статье.


                                                        Но даже с учетом дичи которую может делать функция в ТС это намного проще.


                                                        Ладно бы 10, 100, но в 1000 строк наверняка попутно делается намного больше из заявленного сигнатурой. Т.е. если Вы напишете свою реализацию в 10 строк, то наверняка она не будет обладать тем же поведением, что и 1000 строк (всякие побочные действия)

                                                        Да не, можно же написать чистую функцию в тыщу строк. Ну да, она будет сложная, но и что?


                                                        Ибо 1000 строк простыни — это почти всегда очень дурно.

                                                        Понятно же, что это пример. Ну пусть будет 50 или 100, это же непринципиально. Вопрос в одном — могу ли я прочитав одну строчку понять всё, что мне нужно знать о функции, или мне надо лезть и смотреть что же на самом деле она делает.


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


                                                        В мейнстрим статических языках что-то среднее. Но я бы не сказал что это золотая середина, скорее ни рыба, ни мясо.

                                                          –1
                                                          Что-то видимо у меня не получилось донести ту мысль, что в сигнатуре очень мало информации для столь огромного количества строк кода, и этот код наверняка делает много больше заявленного, либо не делает ничего (индусский код). Если же он делает только то, что можно понять по сигнатуре, то наверняка его можно (и нужно) уместить в гораздо меньший обьем.
                                                          Т.е. принцип «код делает только то, что понятно по сигнатуре» и простыня на 1000 строк — практически несовместимы.
                                                          Хорошо, пусть будет в вашем примере не 1000 непонятных строк, а 5 *очень* непонятных строк с черной магией и прочим.
                                                            +3
                                                            Что-то видимо у меня не получилось донести ту мысль, что в сигнатуре очень мало информации для столь огромного количества строк кода, и этот код наверняка делает много больше заявленного, либо не делает ничего (индусский код).

                                                            Почитайте статью что я скинул. У вас НЕТ способа сделать ничего кроме заявленного, ни в тыщу, ни в миллион строк. Если система типов достаточно нормальная, конечно. И вполне хватает мейнстрим языков (та же скала или раст), где это выполняется точно, и есть чуть менее сильные языки (C#/Java/TS), где это почти наверняка так.


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


                                                            Т.е. принцип «код делает только то, что понятно по сигнатуре» и простыня на 1000 строк — практически несовместимы.

                                                            Я могу показать вам вполне чистую функцию на тыщу строк легко. Первая на 300 не очень информативна (потому что делает IO), но вот вторая — получше: https://github.com/Pzixel/Solidity.Roslyn/blob/master/Solidity.Roslyn/SolidityGenerator.cs#L342


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

                                                              +3

                                                              Если взять какую-нибудь хитрую структру данных, то там суммарно кода под каким-нибудь insert или find может быть 100-500-1000 строк, но по сигнатуре


                                                              foo :: a - > DataStruct a b -> Maybe b

                                                              вы легко скажете, что она делает.

                                                            0

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

                                                              +1
                                                              Ну так посмотри на доку любой JS/python либы там идет описание на пару строчек а дальше перечисление какого типа какой аргумент.

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

                                                    –1

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

                                                      +2

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

                                                        –2

                                                        Go, на мой взгляд, только приближается к мейнстриму. Haskell же страшно далёк от него.

                                                      0
                                                      В javascript выйдет ещё меньше церемоний: можно было бы вместо написания consume использовать метод slice
                                                        0

                                                        Церемонии JS — это одна строчка с именем функции и аргументов, мы ведь специально не рассматриваем тело. Если можно это упростить, то, пожалуйста, покажите как.

                                                          –1
                                                          Я конечно не занимаюсь разработкой на js, но мне кажется, что такой код должен сработать:
                                                          var consume = function (source, quantity) {
                                                            if (!source) {
                                                              return [];
                                                            }
                                                            if (quantity <= 0) {
                                                              return source;
                                                            }
                                                            var first = source.shift();
                                                            return consume(source, quantity - first);
                                                          }
                                                          
                                                            0

                                                            Но ведь строка var consume = function (source, quantity) {, а всё остальное (серое) мы не сравниваем, потому что у автора не было цели оптимально реализовать функцию, более того, он прямо пишет "я не особо пишу на ЖС и точно можно лучше".


                                                            Один из вариантов более симпатичного тела функции:


                                                            var consume = function (source, quantity) {
                                                              let s = 0;
                                                              const index = source.findIndex(x => (s += x) >= quantity);
                                                              return index < 0 ? [] : source.slice(index + 1);
                                                            }
                                                              +2

                                                              Взяли и заквадратили алгоритм… Не надо так делать, пожалуйста!

                                                                +2

                                                                Не говоря о том, что из-за отсутствия TCO в JS оно на списке длиной элементов в двести повалит рантайм из-за переполнения стека.

                                                          –1
                                                          Что опять складывание чисел? Опять создают иллюзию «магии», которая за вас все сделает. И опять игнорируют тот факт, что за «магией» скрывается нетвиальная мат теория, которую следует знать, потому что очень быстро можно столкнуться с «ой, а почему тут за меня не вывели тип? а что надо было явно указывать?».
                                                          И да вся история про «автовывод типов» очень хороша, когда у вас под рукой инструмент, который вам если что подскажет какой тип у данного выражения, но когда вы смотрите код на каком-нибудь гитхабе, то бывает очень «весело» угадывать этот самый тип.
                                                            +7
                                                            Что опять складывание чисел?

                                                            Ну предложите что-то другое. Я вон деревья грузил по ресту — тоже мне говорили что задача не та.


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

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


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

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


                                                            Ну и в любом случае информации не меньше, чем в PR в JavaScript репозитории.


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

                                                              0

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

                                                                +7

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


                                                                type HeadersInit = Headers | string[][] | Record<string, string>;
                                                                
                                                                interface Headers {
                                                                    append(name: string, value: string): void;
                                                                    delete(name: string): void;
                                                                    get(name: string): string | null;
                                                                    has(name: string): boolean;
                                                                    set(name: string, value: string): void;
                                                                    forEach(callbackfn: (value: string, key: string, parent: Headers) => void, thisArg?: any): void;
                                                                }

                                                                То есть хэдеры — это либо просто мапа стринги на стрингу, либо двумерный массив, либо вообще объект со своими методами. Или вот моё любимое:



                                                                Тип поля: string | false | null. Не bool, а false. Вот такие вот завтипы.


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

                                                                  0
                                                                  Тип поля: string | false | null. Не bool, а false. Вот такие вот завтипы.

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

                                                                    +1

                                                                    Ну, "не указан" лучше бы было как undefined указывать, тем более что внутри эти параметры всё равно в string | false | null | undefined превращаются из-за своей опциональности.

                                                                      0
                                                                      "не указан" лучше бы было как undefined указывать

                                                                      Абсолютно не согласен. «Не указан» в данном контексте — это пустой, null, void, если хотите. Ну и, кроме того, в здоровом API, которому не нужен специальный клиент на JS, — undefined немного не в чести.

                                                                        +1

                                                                        Обратите внимание на "вопросики": эти параметры можно реально не указать, и тогда там будет undefined. Зачем тут ещё и null?


                                                                        «Не указан» в данном контексте — это пустой, null

                                                                        А false тогда что означает? :-)

                                                                          0

                                                                          false означает «указан, игнорировать», как я написал в первом комментарии;
                                                                          null означает «указан, значение: пусто» (nullnull — означает дубль пусто-пусто. Кхм. Простите :)
                                                                          string означает «указан, вот значение»;
                                                                          - отсутствие параметра означает «не указан».

                                                                            +5

                                                                            Вас послушать, так там все 3 случая обрабатываются по-разному, хотя на самом деле значения false, null, undefined и даже пустая строка означают одно и то же.

                                                                              0

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


                                                                              Я просто высказался на тему «false» и «null» — это разные сущности, а наличие варианта «false» не предполагает наличие варианта «true».

                                                                    0
                                                                    То есть хэдеры — это либо просто мапа стринги на стрингу, либо двумерный массив, либо вообще объект со своими методами.

                                                                    Не всё так плохо :-)


                                                                    Хэдеры — это всегда объект со своими методами, но в некоторые места API для простоты можно передать мапу или двумерный массив.


                                                                    Вполне нормально для API, написанного исходя из удобства использования, а не удобства реализации.

                                                                      –1
                                                                      API, написанного исходя из удобства использования, а не удобства реализации

                                                                      Угу. Вообще, за API, написанные, исходя их удобства реализации / сопровождения — надо гнать мокрыми тряпками из профессии.

                                                                        +1

                                                                        И где тут простота использования? Мне пришел вот такой объект, и мне теперь нужно паттерн матчиться, чтобы понять, то ли мне массив создавать, то ли в мапу добавлять, то ли метод append дергать. Ну вот из моего кода:


                                                                        export class BaseClient {
                                                                            private configuration: ClientConfiguration;
                                                                        
                                                                            constructor(configuration: ClientConfiguration) {
                                                                                this.configuration = configuration;
                                                                            }
                                                                        
                                                                            protected transformOptions(options: RequestInit): Promise<RequestInit> {
                                                                                const headersToInsert = [["Authorization", this.configuration.bearer]];
                                                                                // ???
                                                                                return Promise.resolve(options);
                                                                            }
                                                                        }

                                                                        Вот как мне теперь вставить этот хэдер? Нужно проверить все варианты что за RequestInit и действовать соответствующе. При чем тут удобство реализации? Этим пользоваться неудобно.