В этой статье рассказывается о том, почему я бросил разработку своего прошлого языка программирования, как новый язык решает проблемы старого, а также  показываются тесты программ, написанных на новом языке. Приветствую, читатель. В одной из своих прошлых статей (Как я 12 лет создавал свой ЯП и компилятор к нему) я рассказал о том, как я создавал свой язык программирования, продолжая его развитие и уже почти выпустив версию 0.2, я понял, что это не тот язык, на котором я хочу писать и я начал обдумывание нового языка. Поскольку это мой где-то 8-й по счёту язык программирования, я задумался "А в чём причина того, что я создал уже столько языков, но никак не могу получить тот, который мне подходит?". Когда проектируешь язык программирования, всё время приходится идти на компромиссы, добавляешь идею в язык, затем вторую, а затем когда добавляешь третью, то она идеально сочетается и с первой и со второй, но не с двумя одновременно, и приходится выбирать какую комбинацию из идей использовать. Я решил полностью абстрагироваться от различных идей, наличие которых я считал обязательным для языка которым я хотел бы пользоваться, и заново посмотреть на то, что мне действительно нужно. Вот список идей, которые я считал обязательными для языка: обобщённое программирование (хороший пример - Haskell, плохой пример - C) язык должен хорошо подходить для того, чтобы легко было создавать компилятор, создающий производительные приложения (хороший пример - С, плохой пример - Haskell) наличие обобщённых типов статическая типизация данных возможность особо не думать об одних данных во время работы с другими данными (как например в Haskell не нужно думать о том, что изменение одних данных повлияет на другие, поскольку все данные иммутабельны, или как в моём прошлом языке, если одни и те же данные находятся в 2-х объектах, то при попытке их изменить, для объекта через который данные меняются, создаётся копия этих данных) В результате размышлений, вот к каким выводам, по каждому из пунктов, я пришёл: обобщённое программированиеЯ не готов отказаться от этой идеи. Писать и использовать библиотеки, написанные таким образом, является для меня неотъемлемой частью программирования. язык должен хорошо подходить для того, чтобы легко было создавать компилятор, создающий производительные приложенияКогда я начинал создавать языки, мне было 19 лет и в этом возрасте мне хотелось, что бы всё было "быстрее, выше, сильнее", хотя я давно вышел из этого возраста, почему то в процессе создания языков, я всё ещё ориентировался на то, что приложения должны иметь крайне высокую производительность. Сейчас же я считаю, что приложения должны иметь достаточную для решаемых задач производительность, а в языке должна быть возможность оптимизации кода, если его производительность оказалась недостаточной. В процессе создания языков, я сотни раз сталкивался с ситуацией, когда мне хотелось реализовать в языке какую либо идею, но я понимал, что её реализация ударит по производительности и я отказывался от идеи, сейчас же я бы принял другое решение во многих подобных ситуации. наличие обобщённых типовМне очень нравятся обобщённые типы и среди всех виданных мною идей обобщённого программирования, это одна из самых любимых. Во всех моих языках были обобщённые типы (в том или ином виде), но во время проектирования своих языков, я периодически сталкивался с ситуацией, когда хочешь реализовать некую интересную идею, но она не совместима с обобщёнными типами и приходилось от этой идеи отказываться. Я до последнего хотел оставить обобщённые типы в языке, но когда уже стали вырисовываться черты нового языка, я понял, что обобщённым типам в нём не место. Отказ от обобщённых типов, было самым тяжёлым решение в процессе проектирования языка. статическая типизация данныхМне нравится, когда язык немного (именно немного) урезает возможности разработчика, чтобы код был безопаснее и во время компиляции можно было находить ошибки. Один из таких способов - статическая типизация данных. В моём прошлом языке для аргументов не было обязательным указывать конкретный тип, а можно было лишь описать то, какое поведение он должен иметь, иногда такому поведению мог соответствовать лишь один тип, иногда несколько, а иногда все типы. В новом языке я решил оставить подобный подход, но во первых упростить его, поскольку описания поведения было достаточно бойлерплейтными, а во вторых разрешить переменным и результатам функций иметь объекты не конкретного типа, а любого типа который соответствует некому поведению, но такую типизацию уже не назовёшь статической, поскольку за переменной не закреплён конкретный тип, но и назвать это динамической типизацией - язык не поворачивается, поскольку за переменной всё таки закрепляется некая информация о типе, да и проверка типов во время компиляции, а так же оптимизации на основании типа - имеются. Получилось что-то между статической и динамической типизацией, но всё же ближе к динамической типизации, поэтому можно сказать, что я изменил свой взгляд на то, какая типизация мне больше подходит. возможность особо не думать об одних данных во время работы с другими даннымиТо, как это было реализовано в прошлом моём языке, было крайне удобно и не создавало никаких проблем и конфликтов, а лишь наоборот, предоставляло преимущества там, где я этого не ожидал. Поэтому эта идея остаётся и в новом языке, с такой же реализацией. Я уже написал, что мой предыдущий язык мне не подходит, но какие именно с ним проблемы и как новый язык их решает? Проблема 1: в языке мне не хватает ООППроблема 2: требование групп не проверяются во время компиляции ПодробностиДля начала, для тех кто не читал статью про мой предыдущий язык, я объясню что такое группы. Группа - это абстракция над совокупностью типов имеющих схожее поведение. Больше всего группы напоминают классы типов из Haskell, если вы не знакомы с Haskell, то во многих языках есть интерфейсы, интерфейсы и группы похожи, но это не тоже самое. Группа может декларировать некоторое поведение и все члены данной группы должны его реализовывать, но в предыдущем моём языке это поведение записывалось в комментариях к группе, а за тем корректно ли тип реализовывает данное поведение, должен был следить разработчик и компилятор в этом никак не помогал. Я не употребляют алкогольные напитки, но такое ощущение, что когда я принимал решение о добавлении таких групп в свой язык, я был сильно пьян. РешениеВ новом языке в группах можно указать функции, которые членам группы нужно реализовать при добавлении в группу и только если какое либо поведение нельзя реализовать с помощью функций, только тогда нужно указывать поведение в комментариях к группе. Компилятор теперь контролирует то, реализовывает ли тип, функции группы и правильно ли он это делает. Если для членов группы можно написать реализацию некой функции, то такая возможность есть,  а если для какого либо типа эту функцию нужно изменить (переопределить), то это так же можно сделать. Также в группе можно указать, что все её члены должны быть и членами других групп, такие группы называются - субгруппами.Возможно у вас возник вопрос: "Проблема 2 решена, а при чём здесь ООП?".Отвечаю: группы можно представить как классы, функции групп как методы, а субгруппы как родителей текущего класса. Сразу оговорюсь, что определять и переопределять функции из группы, можно только для типов, поэтому при наследовании дочерний классы не может переопределить методы родителя из-за чего при компиляции не может возникнуть проблемы ромба, но конечно на логическом уровне она может возникнуть. Проблема 3: мне не нравится, что данными из модулей можно манипулировать ПодробностиВ предыдущем языке поведение импортируемых модулей можно сильно изменить даже с помощью другого импортируемого модуля, в некоторых случаях это прикольно, но огромная проблема для надёжности приложений. РешениеВ новом языке такие возможности отсутствуют. Проблема 4: мне не хватало гибкости языка ПодробностиВ предыдущем языке очень часто при создании какой либо новой возможности, мне приходилось создавать встроенные функции в компиляторе, хотя в других языках для этого достаточно возможностей языка. Решение Система типов в новом языке совместно с тем, что пользовательские типы объявленные через ключевое слово type, всегда являются гетерогенным массивом - решение проблемы. Проблема 5: работа с ошибками Подробности Мне нравится, когда работа с ошибками ведется с помощью оператора try catch, но в предыдущем языке ошибка возвращалась в качестве результата функции, либо приводила к закрытию приложения. Такой подход не удобен, но это один из тех случаев в котором я жертвовал удобством ради производительности. РешениеВ новом языке работа с ошибка ведется с помощью оператора try catch, а так же если произошло исключение, но его ни кто не ловит, то после вывода сообщения об ошибке, будет выведен полный стек вызова функций до появления исключения. Проблема 6: я постоянно упираюсь в особенности языка C,  хочется вернуться на LLVM IR ПодробностиМой первый язык компилировался в ассемблер (FASM), затем я перешёл с ассемблера на LLVM IR, а затем на C. После перехода на C всё стало гораздо проще, но я не хотел привязываться к какому либо компилятору и поэтому решил ориентироваться на стандарт C, чтобы можно было компилировать код разными компиляторами (например debug версию tcc, а release версию gcc) и под разные платформы.Знаете чему равен результат выражения sizeof(bool)? Неизвестно! Потому, что размер bool не определен и если ваш код думает что размер bool равен 1 байт, то ваш код содержит ошибку. Тоже касается и char, int, long. В C есть типы int8_t, int16_t и т.д., но они никак не помогают, поскольку все функции из стандартной библиотеки используют char, int, long, size_t и т.д., однажды в стандартном модуле я нашёл код, который корректно работал на 64-битной архитектуре процессора, но выдавал бы ошибку на 32-битной, эта ошибка была связанна как раз с тем, что размеры типов не определены.Так же я хотел оптимизировать стандартный модуль и для этого я думал использовать различные расширения языка из gcc и clang, определяя можно ли эти расширения использовать с помощью макросов и если нельзя, то использовать альтернативный способ. Например я хотел использовать SIMD вектора, но в gcc и clang они ужасно мало функциональны по сравнению с LLVM IR, как альтернативу можно использовать immintrin.h, но это в 100 раз менее удобно и только для x86. Как итог, считаю переход с LLVM IR на C - ошибкой. РешениеВ новом языке вернулся к LLVM IR. Проблема 7: компиляция и проверка на ошибки модулей ПодробностиВ предыдущем языке модули не компилировались в C и не проверялись на ошибки, а просто исходный код модуля упаковывался в один файл. Это был связанно с тем, что язык был чрезмерно абстрактным и многое было понятно только при компиляции конечного приложения, поэтому технически не было возможности компиляции модулей в код на C и проверки их на ошибки. РешениеВ новом языке нет помех для компиляции модулей. Модули компилируются в LLVM IR с некоторыми пометками, необходимыми для конечного приложения. Предыдущий язык назывался - Cine. Название было сгенерировано генератором случайных имён и было неудачным, к тому же я произносил его как Кайн, а люди которые мне писали всегда писали его как Сайн. Поэтому над новым названием я думал получше.Название нового языка - Shar (Шар), в честь богини ночи из вселенной Forgotten Realms. Так же получается красивое названия для компилятора: Shar compiler - sharc (похоже на английское слово shark - акула), а также прикольное расширение файла для модулей: Shar module - sharm. Когда я опубликовал статью о Cine, я указал, что завёл блог в котором буду рассказывать о программировании на Cine, на что мне многие писали, что нужно было подготовить к релизу какой либо документ, по которому можно будет изучить основы. В этот раз я так и сделал. Документ не большой (если открыть в Firefox и нажать печать, показывается 8 страниц), только о самых основах и рассчитан на то, что вы уже имеете навыки программирования на каком либо языке.Ссылка на документ.Для просмотра реального кода на Shar вы можете посмотреть исходники стандартного модуля, а также исходники sharc, который уже написан на Shar.Ссылка на исходники стандартного модуля.Ссылка на исходники sharc. Как я уже писал выше, в новом языке я не особо заботился о производительности и по этому нужно посмотреть на сколько всё плохо. Для тестирования я буду использовать бенчмарк n-body, с сайта The Computer LanguageBenchmarks Game, хочу сразу обратить ваше внимание, что этот тест синтетическая-числодробилка, реальные приложения будут работать значительно лучше (например компилятор sharc, который уже написан на Shar, работает быстрее чем я сам того ожидал). Для информативности, я буду сравнивать производительность с производительностью программы написанной на C, Python, а также просто ради интереса укажу производительность реализации на Cine (заново замерять не буду, а результат возьму с блога про Cine, там же и исходники (ссылка)), все программы не оптимизированы. Замеры производились трижды и брался тот результат, который не самый быстрый, но и не самый медленный. Результаты: Язык Способ компиляции Время сек.(меньше лучше) Shar sharc -o n-body.ll -m /usr/include/shar/STD.sharm -s main.sharopt -O3 --mattr=+avx2 n-body.ll -o n-body.bcclang -O1 -march=native -lm -ldl -pthread n-body.bc /usr/lib/libshar-os-api.so -o n-body 42.669 C clang -march=x86-64 -O3 -lm 3.966 C clang -march=native -O3 -lm 3.698 Python python -OO 415.093 Cine gcc -lm -O3 -march=native 6.32 Так же я писал, что должна быть возможность оптимизировать программы, поэтому для демонстрации данной возможности, я оптимизирую данную программу, но сразу уточню, что мне не хотелось долго возиться с оптимизацией, поэтому я не оптимизировал программу по самое не балуй, при желании, это программу можно ещё сильнее оптимизировать. Так же я уточню, что оптимизация честная и все полезные расчёты из не оптимизированной программы, происходят и в оптимизированной, никаких предрасчётов или переходов от чисел с плавающей точкой двойной точности к числам с плавающей точкой одинарной точности (этим грешат некоторые оптимизированные версии с The Computer LanguageBenchmarks Game). Способ компиляции такой же. Результат: 3.034 сек. ИтогВсе поставленные задачи были достигнуты, результатом доволен. Сcылкиhttps://github.com/SharDeveloper/sharchttps://github.com/SharDeveloper/shar-standart-modulehttps://github.com/SharDeveloper/libshar-os-api_linux-x86_64https://github.com/SharDeveloper/kate-shar-highlighting