Меня просили объяснение доступное и для тупых студентов. Отвечаю. Похоже что непонятна даже обсуждаемая тема и проблемы. Первое, что нужно понять - у недоязычков вообще нет никакого выбора. Я постоянно начинаю говорить о сравнении подходов и о С++, но исключим С++ из обсуждения. C++ - это аномалия, которая возникла случайно. Об этом я расскажу позже. Задача стоит следующая, есть код: ```rust f(arg: T) { arg.f(); } ``` T - это генерик-параметр. И первое, что нужно осознать - в базовой модели это один тип. Единственный тип. Базовая модель воспринимает генерик-функцию за обычную функцию. У обычной функции все параметры аннотируются единственный, конкретным типом. Именно поэтому генерик-параметр на самом деле не является параметром. Это специальный вид типа. Подобный финт есть в си с указателями. ```cpp f(void * arg) { arg->f(); } ``` void * - это некий вид указателя, который лишён уникальных свойств. Тех свойств, что определяется типом при указателе. И T - это просто "развитие" данной идеи, данного костыля. Почему так сделано очевидно. Нужно каким-то образом в одну функцию впихнуть множество типов и возможно это только путём потери всех уникальных свойств всех входящих типов. ## Базовые свойства * T является ОДНИМ ТИПОМ, всегда. Одним - значит однозначно определён без параметризации, такого понятия как "параметризация" здесь не существует. * С т.з. базовой модели генерик-функция не является множеством функций, а является одной функцией. Эти два свойства следуют друга из друга. * Каждый тип - это множество каких-либо свойств. Соответственно, T - общий тип, либо пересечение всех множеств свойств. ## Почему везде написано враньё? Это не совсем враньё, у генерик-параметров есть назначение. Правда назначение это никак не связано с функциями, ограничениями(почти) и прочим. Проще объяснить на примере недоязычка раста. И даже не на примере самих генериков, а на примере лайфтаймов. Зачем нужны лайфтаймы? Они нужны для связи входов и входов с выходом. Рассмотрим пример: ```rust f(x, y) { return x; } ``` Здесь есть два входа - это аргументы для x и y и один выход - этот выход алиас x. Но. Здесь мы выходим на новое свойство всех недоязычков. Никто из недоязычков не проводит(полностью) анализ тел функций. В связи с этим недоязычок не может связать возврат и x. Именно для решения это проблемы и рождён костыль - лайфтаймы. Он позволяет руками описывать эти связи. Механизм прост - создаётся некий уникальный флаг, которым аннотируются алиасы. Все алиасы одно и того же объекта аннотируются одним и тем же уникальным флагом. Тоже самое касается и генерик-параметров. Задача их такая же - просто связать входы и выходы. Необходимо этому потому, чтобы вывод типов смог вывести тип максимально просто. На какой-то сложный анализ, да ещё и на рекурсивный анализ тел - недоязычки просто неспособны. На базе этого механизма может быть построен какой-то примитивный анализ/трансформация типов. Но нас не должно волновать данное обстоятельство. Здесь важно только то, что подобный подход создан специально для ухода от решения данной задачи и крайне ограничен. И никогда из него нельзя сделать что-то полноценное, ведь если что-то будет - подобный подход использован не будет. ## А где же трейты? Здесь так же нужно понять, что трейты не имеют никакого отношения к обсуждаемой теме. Никогда ненужно слушать пустоголовых сектантов, которые кидаются базвордами совместно с нелепыми потугами что-то кукарекать. Трейты - это совершенно другая история. Это история про vtable. Ситуация следующая. Вернёмся к примеру: ```cpp f(void * arg) { arg->f(); } ``` Это код не работает и не может работать. Метода f нету. Нам нужно как-то аннотировать arg, но мы этого сделать не можем. Если мы чем-то его аннотируем - это превратиться в какой-то конкретный тип, а мы хотим принимать множество типов. Решение простое. Если мы хотим вызвать f, то у всех типов должно быть этот метод(свойство). Таким образом нам нужно ввести какой-то void, только с ещё и f в качестве общего свойства. Т.е. сумтип не от всех типов, а от тех, у кого есть данное свойство. И передать туда можно только те типы, у которых есть данное свойство. Здесь прогресс разделился. Появились сумтипы как типы с операциями объединения и прочее. Но данный подход имеет проблемы, которые не особо прижились в скриптухе. Трейты это другой подход. Все его знают под банальной объектной моделью известной всем. Схема проста донельзя. Мы не будем объединять какие-то типы - мы создадим новый тип. Этот тип будет описывать интерфейс, а так же с ним можно будет связать другие типы. Все связанные с даннам типом типы могут передаваться как данный тип, т.е. приводится к нему. Таким образом ничего не меняется. ```cpp f(base * arg) { arg->f(); } ``` Тип остаётся одним единственным, функция остаётся одна. Реализуется это посредством vtable. При этом никакой разницы между трейтами и тем же наследованием через базу. Ключевое здесь то, что создаётся новый тип. Этот тип используется для тайпчекинга тел. К этому типу привязываются другие типы(т.е. нужно каким-то образом явно связать другой тип с этим типом) и через эту связь происходит проверка. ## Но сектанты орут, что разница есть На самом деле нет. Эта разница минорна. Эту разницу я показывал в предыдущей пасте. Трейты более примитивны, там нету всех эти заморочек с наследованием, видимостью, кастами и прочим. Это просто набор функций, которые валяются отдельно. Сами типы не таскают с собою vtable и она прикручена сбоку. Поэтому адепты берут примитивные примеры и показывают, что примитивная херня проще более сложной. Что очевидно. Причём сектанты зачастую сравнивают жопу с пальцем. Во-первых везде и всюду всё обмазано интерфейсами, а не абстрактными классами. Интерфейсы это такой же набор функций и там уже нет большинства проблем. Поэтому во всей мейнтримной скриптухи интерфейс интерфейсом погоняет. Во-вторых, основное маня-преимущество заключается в том, что сами данные никак не связаны с трейтами, т.е. к той самой базе привязывается имплементация. В реальности же это не особо является проблемой. Нет никаких проблем зависеть от интерфейса, зависеть от множества интерфейсов. Никаких маня-проблем с множественным наследованием там нет. Никаких проблем с композицией интерфейсов нет. В любом случае, понять нужно то. Что вся эта разница не меняет базовых свойств, о которых я говорил. Функция одна, тип один, vtable, необходима привязка к базовому типу. # Продолжение тут. Адепт попытался там на меня побалаболить, пытаясь меня разоблачить. Но опять же, даже он осознал, что заход не удачный. Но в целом его можно понять - fsb4000 решил не уточнять то, что уточнил я. То, что эта паста, которая я не дописал и допишу потом. Ладно, я её допишу. ## у меня там не всегда vtable В общем, я уже не раз говорил, что факт наличия vtable ничего не значит. Я даже не понимаю зачем об этом болтать, потому как уже давно во многих языках есть девиртуализация. И даже если где-то должна быть vtable - её может и не быть. Поэтому, в современных реалиях уже давно нужно забыть эту херню протухшую уже лет 20-30 назад. Сейчас важна только семантика, и я обсуждаю только семантику. Повторю ещё раз. С тз языка важно не то будет ли vtable в результирующем кода, а то будет ли она семантики. Потому как если её не будет реально - семантика её наличия останется. Так же, в прошлой пасте я говорил, но повторю и тут. Во многих недоязычках добавлена ещё одна стадия, особенно это касается недоязычков с ворованным кодогенератором(типа раста). Т.е. недоязычок не пытается создавать уникальные тела функций на уровне языка, но это происходит на уровне кодогенрации. ## затирание типов как бы очевидно Правда в реальности очевидно другое. Я уже не раз говорил сектантам про затирание типов, семантическое vtable и прочее. Но каждый сектант(да и в сектантской пропаганде) уверяет в том, что никакие типы не затираются. Проверить это можно где угодно. Если же сектант согласен, что типы затираются - он уже обречён при попытка выдать систему типов своей скриптухи за что-либо серьёзное. ## сектанты орут - "анализ тел атавизм си" Это, опять же, полная чушь. Оснований этим потугам нет. К тому же, обсуждаемая тема находится далеко за пределами анализа тел. Подробнее об этом я говорил в предыдущей пасте. В этом так же вернусь к этому, когда буду говорить о С++. ## недоязычок создан для великих свершений, а не для лаб - это причина его убогости Это просто опровергается тем, что ничего, кроме лаб, на этом недоязычке нет. Тем, что самые большие и сложные проекты на С++, где этот анализ есть. Тем, что мусорный недоязычок собирается уже дольше С++, хотя не делает и 2% работы производимой компилятором С++. Это опровергается ещё и тем, что сам по себе анализ тел ни на что не влияет. Ведь анализа тел нету не только для отдельных инстансов генерик-функций, но и для самих генерик-функций. Сектанты выводят лайфтаймы/типы возврата руками. Так же, сам сектант ссылается на то, что недоязычок при помощи llvm-api всё равно генерирует инстансы генерик-функций. При этом врованный llvm обязан анализировать и оптимизировать каждый инстанс. И этого анализа там куда больше, чем далее это нелепый недоязычок. Это одна из причин, почему эта скриптуха компилируется уже дольше C++. И вот возникает вопрос. Почему на уровне llvm проводить анализ каждого инстанса - это хорошо, а на уровне фронта - нет. Причём опять же, об этом даже речи не шло. Речь шла о выводе типов хотя бы уровня хаскеля. Там та же скриптуха с vtable(семантической и не только). При этом, существуют механизмы обхода проблемы. И если компиляторы С++ и сам С++ спроектированы без оглядки на инкрементальный анализ, то почему недоязычок это не осилил? Инкрементальный анализ полностью решает проблему. Ладно бы инкрементальный анализ - в чём проблема реализовать опциональный анализ? ## ко-ко-ко ЭТО не потому, что не осилили Всегда нужно понимать, что адепт любой мусорной скриптухи всегда будет усираться, доказывая что на самом деле всё осилили, просто это ненужно. Каждый из адептов - ленивый мамкин супермен. Он бы сделал всё, да просто ненужно. Эти потуги, очевидно, ничего не стоят. Зачем кому-то вообще заниматься созданием чего-то мощного и сложного, если можно родить примитивное говно, да к тому же ещё более лучшее? И именно это работать не может. Вообще. Если человек сделал что-то сложное - он доказал, что может сделать примитивную херню. И его мотивация всегда за пределами "не могу сделать примитивней". А вот когда обратная ситуация - мотивация как раз таки в 99% именно в "не смоглось". ## раст - недоязычок с отличным выводом типов внутри функции Это полная и нелепая чушь. Мало того, что это попросту враньё, ведь никакого вывода типов в недоязычке нет. К тому же, тот, кто несёт подобную херню - попросту невежда, потому как вывод типов возврата - это и есть вывод типов. Причина тому проста - недоязычок состоит из одних функций. Т.е. функция - это единственная логика, которая может трансформировать тип. Функция это - преобразование типа вида: (Tin0, ...) => Tout. Вывод типов в рамках тела функция - это ни что иное как вызов тех самых преобразований типов. Т.е. взять тип аргументов, преобразовать их в типы выхода и протолкнуть эти типы далее, через присваивание, либо использование объекта по месту. Если же вывода типа возврата нет - никакого вывода типов нет. Это первое, что должны осознать балаболы в интернете. Сейчас каждый сектант руками пишет эти преобразования. Недоязычок тупо берёт и подставляет эти типы. Это уровень лабы первого курса. Самое важное. Даже если бы недоязычок мог в вывод типов возврата - это бы не сработало. Причина проста - в недоязычке самая примитивная система типов. Это значит, что трансформации типов там максимально примитивны. В основном это всё сводится к чему-то вида Tout = Tin, Tout = const(т.е. любой произвольный тип), Tout = const\ Т.е. проблема недоязычка не в том, что там нету вывода типов. Там нету вывода типов в рамках мусорной примитивной системы типов. И это первая и основная проблема - мусорная система типов. Это признак того, что не осилили. # Продолжение следует. Буду потихоньку дописывать пасту. Далее ещё раз подниму тему "почему трейты?". Далее "почему С++ - это аномалия?".