RTS — Реактивный язык программирования свободных структур данных

в 6:15, , рубрики: Realtime, RTS, Rust, интерпретатор, интерпретация, Программирование, реактивное программирование

Представляю вам свой open-source проект — RTS (Real‑Time Script). Это реактивный язык свободных структур данных, опирающийся на новый подход к программированию. Проект я начал и продолжнаю писать на данный момент в одиночку начиная с 6 Декабря 2023 года. Пол года было потрачено на создание концептов и ещё пол года на саму реализацию.

Далее будет рассказ, он будет довольно подробный и я надеюсь, не будет сумбурным. Сама цель моего рассказа, объяснить работу RTS и концепции к которым меня привела моя работа в этой области. Это мой первый пост как на Хабре, так и в целом более‑менее публичный про RTS. Поэтому прошу отнестить с пониманием. В случае, если у вас есть критика, предложения и т. п., то рад буду обсудить и ответить.

Цели проекта

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

Основные причины побудившие начать проект:

  • Желание написать работающий язык программирования с полезной нагрузкой.

    У меня уже было около 4-х попыток создания разных языков на других языках программирования, однако всё это не имеет практического смысла, кроме как обучение самого себя. Ещё ранее, все программисты, с кем я советовался, говорили, что это бесполезно. Осознание написать что‑то другое произошло в тот момент, когда ты понимаешь, что с каждой новой цепочкой переписывания одного языка на другом, становится только хуже. Активно так стали делать с 1960-х годов, и на данный момент упёрлись в сильные ограничения из‑за засорения функционала, так и по потере скорости и утечкам памяти; Не спорю на счёт того факта, что можно написать тот же Rust, но он всё равно написан на тех же самых старых концепциях и механизмах работы. Поэтому идеальным вариантом стало бы отделение в совершенно другую ветку от первых языков программирования. На данный момент это возможно сделать только путём написания интерпретатора или компилятора на чистом машинном коде. Сделать это возможно, но это не вопрос этой статьи.

  • Изучение возможностей языка программирования Rust в замен опыта на C/C++ и Java.

    До момента написания RTS, я около 3-х лет писал на C/C++ и около 2-х лет на Java. В один момент я понял, что я просто сгораю при написании кода на этих языках программирования. Это не связано с удобством, синтаксисом языка. Довольно сложно объяснить конкретную причину, которая привела меня к Rust. Изначально я думал, что он не совсем подойдёт для моих задач, однако со временем, я понял, что это новый, современный подход написания программ, но только внешне. Сейчас же, Rust отлично выполняет все задачи RTS, а также показывает хороший результат работы, об этом далее.

  • Создание ответвления от парадигм программирования после 1960-го года.

    Что же такого случилось в 1960-м году? Если в двух словах, то это создание Теоремы Бёма — Якопини, что повлияло на развитие структурного программирования. И что же плохого в этом, спросите вы? Да ничего, правда больше никто не развивает неструктурное программирование. И можно сказать, что это неудобно, создаёт кучу неконтролируемого кода, но только потому, что вы не пробовали и даже не задумывались об этом подходе программирования. Я не говорю о том, что что‑то лучше или хуже, я говорю о том, что должно быть само собой разумеющееся поддерживать альтернативу. Как далёкий пример — синхронное и асинхронное программирование.

  • Создание ветки из трёх современных языков программирования: Ассембер → Компилируемый → Интерпретируемый.

    Итак, в двух словах о направлениях:

    • Ассемблер — это низкоуровневый язык, который в последующем преобразуется в машинный код, понимаемый компьютером;

    • Компилируемый — собирающий программу заранее;

    • Интерпретируемый — запускающий программу в реальном времени без предварительной сборки.

    Написание одного языка сломало бы саму концепцию чего‑то нового и альтернативного. Поэтому я решил написать сразу всю ветку от низкоуровневого, до двух высокоуровневых. В данной статье мы будем рассматривать только интерпретируемый, но надо сказать, что прототип компилируемого языка работает в ~2.5 раза лучше GCC C. Также хотел бы привести статистику разницы инструкций в ~4.57 раз. Опять же это только прототип, ещё есть большое количество работы, но лично для меня это большой прогресс. Смысл же всего этого не просто показать что вот оно есть, а сделать все три языка способными собирать машинный код напрямую и в совершенно новой парадигме программирования, чтобы решить вышеописанные проблемы сегодняшнего подхода. Это избавит от потерь скорости, а также должно максимально убить утечки памяти, банально даже по причине уменьшения количества инструкций и ветвления.

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

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

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

Технические детали

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

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

  • Main — Модуль входа в программу; В нём можно выбрать путь работы, например чтение файла, скрипта, работа с пакетным менеджером. В основном он задаёт только проход к функционалу;

  • Tokenizer — Его задача читать текст и делить его на токены. Токены являются самой малой единицей информации, которая может нести смысловую нагрузку. Токеном может быть всё, начиная от слова или цифры и заканчивая символами. Так вот, токенайзер занимается определением их по тексту, чётко следуя синтаксису RTS. Поэтому всё чтение токенов происходит только через его механизмы. Частично может определить типы данных прочитанных токенов;

  • Parser — Полноценно определяет структуры и типы данных из токенов. Его главная задача, взять все токены из токенайзера и начать подготовку к выполнению кода, обеспечивать его запуск; Тут объявляются изначальные аргументы, цикл работы интерпретатора и работает выход из неё. Здесь могут быть линейные структуры, записанные в одну строку, могут быть вложенные структуры;

  • Structure — Это подмодуль Parser и он занимается основной реализацией всех возможных структур, их работой и взаимодействием. Его задача контролировать и описать каждую структуру настолько точно, насколь это возможно. Любые знакомые нам типы данных, например как методы, классы, ячейки памяти, всё объявляется через этот магический механизм структур;

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

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

Чтобы понять насколько гибкий интерфейс предоставляет RTS, прошу внимание на код (в данном случае это возможный пример):

print(123) # выводим 123 через стандартную процедуру без n

println(any: Any) # создаём структуру и передаём туда список любых структур
  print(any,'n')

println(123) # выводим 123 через кастомную процедуру с n

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

cycle
  a = 0
  ? a < 10
    println(10)
    a += 1
    go(1)
cycle()

В данном примере нам выведет 10 раз по 10, но т.к. каждый раз использовать go(1) и подниматься на верх блока нам не хочется, да и возможно мы настролько ленивы для использования a += 1, то мы могли бы просто создать хитрую реализацию структуры while или for и использовать их. Т.е. даже самые мельчайшие вещи вы можете поменять. А возможно задача настолько специфична, что и реализация в коде будет совсем под другие нужды и механизмы. Как было сказано выше, вам вовсе не обязательно быть гуру программистом, чтобы писать необходимый вам код.

Как работают интерпретация и RTS

  • Код выполняется в реальном времени:

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

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

    • Отказ от обработки ошибок. Вместо их обработки упор делается на максимальную отказоустойчивость: Ошибки просто невозможны; они отрицаются как концепция. Если оглянуться вокруг, то в жизни всё работает именно по этому принципу, когда ошибок просто не существует. Но что тогда есть в противовес? В таком случае, возможно только неожиданное поведение и неопытность самого программиста. Но как вы помните, из описанного ранее, есть определённое константное поведение структур, и вы также можете написать небольшие механизмы для необходимой вам отладки. Если же вам не нужна отладка и вы точно следуете синтаксису и константам, что очень просто, то вам ничего не надо обрабатывать и вы можете сосредаточиться только на вашем проекте.

  • Язык хранится отдельно и не мешает:

    • Отсутствие необходимости многократной компиляции: RTS избавляет от необходимости компилировать программу каждый раз при внесении изменений. Просто напишите код, и он будет готов к выполнению. Всё, что требуется для работы, — это сам интерпретатор, что упрощает процесс разработки, развёртывания и тестирования;

    • Гибкость настройки перед запуском: Интерпретатор позволяет конфигурировать его поведение перед запуском программы. Вы можете задавать кастомные параметры запуска, использовать бинды, специфичные настройки окружения и другие инструменты, подходящие для вашего проекта. Это позволяет адаптировать RTS к любым сценариям работы;

    • Поддержка менеджера пакетов: Встроенный менеджер пакетов предоставляет возможность управлять зависимостями и расширениями проекта в режиме реального времени. Это делает настройку окружения простой и интуитивно понятной, давая разработчику полный контроль над используемыми компонентами;

    • Многозадачность и изолированность: RTS позволяет запускать несколько проектов, программ или скриптов одновременно. Они функционируют независимо друг от друга, не вступая в конфликт и не создавая помех. Это особенно удобно для работы с различными проектами в единой среде или при тестировании взаимодействий между ними.

  • Написание свободных структур:

    • Универсальность и свобода выбора: В RTS структурами данных может быть буквально всё, что необходимо разработчику: переменные, константы, final-поля, списки, массивы, классы, функции, процедуры и многое другое. Такой подход исключает навязывание фиксированных типов или форматов, предоставляя пользователю свободу в создании любых концепций и абстракций, подходящих для решения задач;

    • Гибкая обработка с поддержкой асинхронности и отказоустойчивости: Каждая структура может обрабатываться с использованием специальных флагов, позволяющих задавать её уникальные свойства и поведение. Это включает поддержку асинхронного выполнения, что критически важно для современных высоконагруженных систем, и отказоустойчивость, обеспечивающую стабильность работы даже в случае возникновения неожиданных ситуаций;

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

    • Реактивное взаимодействие структур: Структуры в RTS могут динамически взаимодействовать друг с другом, реагируя на изменения в реальном времени. Это обеспечивает мощные возможности для реализации реактивных систем, где изменения одной части программы автоматически приводят к обновлению связанных элементов. Такой подход повышает гибкость и упрощает разработку сложных взаимосвязанных систем.

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

Синтаксис и примеры кода

Итак, рассмотрим пару важных примеров, на которых вы сможете быстро освоиться в основных моментах.

Начнём с простого, с комментариев:

# комментарий
  всё ещё он
    тоже он;

# вложенность была меньше, 
  мы получили разделитель;
  комментарий был закончен.

# а это просто линейный комментарий

println(10) # можно
  и так, если необходимо

Как объявить структуру?

# Линейная
a = 10
# Вложенная
b
  10
  20
  30
println(a) # 10
println(a.0) # 10
prinln(b.2) # 30
println(b.30) # круто, ничего не будет, просто n

Что за точки и обращения к структурам?

# Допустим, создадим простейшую структуру
a
  b
    20

# Пример статических ссылок
println(a.0.b)   # = a.0.0 -> 20
println(a.0.b.0) # = a.0.0.0 -> 20

# Пример динамических ссылок
c = 0
println(a.[c].[c]) # = a.0.0 -> 20

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

Хочу использовать методы...

# Тогда просто создадим структуру типа функции
ab(a: UInt, b: Int)
  = a+b
c = ab(10,-20) # Получим по итогу 10 типа Int
  Это получится потому, что = вернёт из структуры тип результата a+b

# Пример ограничения типа в результате
ab(a: UInt, b: Int) -> UInt
  = a+a
c = ab(10,-20) # Получим 0, поскольку это константное поведение UInt,
  т.е. всё что меньше 0 в UInt будет автоматически преобразовано в 0.

А что если я хочу преобразование меньше 0?

# В RTS есть ключи-модификаторы для доступа структур;
  Например:
a      # Это final
a = 10 # Теперь это constant
b = 10 # Это сразу const
c~ = 10 # Мы получим variable только со сменой значения
c = 20  # Будет 20 типа UInt
c~~ = 10 # Мы получим обычный variable
c = -20  # Тип поменяется на Int

Ещё можно сделать явное преобразование типа для структуры:

a~~ = 10 # Это UInt
a = String(a) # Теперь это String

Можно использовать форматировние строк:

a = 10 # UInt
b = f"{a}" # FormattedString будет преобразован по итогу в тип String
println(b) # Выведет String b

Можно выводить типы данных для отладки:

a = 10 # UInt
b = type(a) # b будет типа String
println(b) # Выведет строку UInt

Ветвления

# Пример 1
?
  println(1) # сработает это
?
  println(2)

# Пример 2
a = true
? a
  println("true") # сработает это
?
  println("false")

# Пример 3
b = false
? a = b
  println("true")
?
  println("false") # сработает это

# Пример 4
? a = false
  println("if")
? b = true
  println("elif")
?
  println("else") # сработает это

Как видно, ветвления похожи на простые вопросы, как при общении. Планируется также охватить всю полноту логических выражений. Для этого будут добавлены дополнительные логические операторы по мимо ! | &. Однако возможен вариант перехода на троичную логику, что не является темой этой статьи, но если в двух словах, она бы работало интуитивно правильнее и ближе к реальной жизни.

Производительность и тесты

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

Все основные замеры были проведены на системе ArchLinux в x86-32 и x86-64 условиях. Для замеров и тестов использовались perf, hyperfine, upx, strip. Расшифровывая - проводились сравнения работы в сжатых условиях, не сжатых условиях, разных архитектур. Итогом работы стало:

  • RTS не будет поставляться в сжатом виде, вы можете это сделать сами при необходимости, променяв скорость работы на размер RTS. Это будет полезно на микро-компьютерах;

  • RTS будет использовать оптимизации Rust сборки. Имеется ввиду, что есть определённые настройки при сборке программ на Rust и хитрости по реализации кода, которые вполне справляются со всеми требуемыми задачами;

  • RTS работает в 2 раза быстрее и в 3 раза стабильнее Python. Имеется ввиду работа на современных процессорах, ветвления, количество инструкций на процессор и пропускная способность на процессор, а также утечки памяти.

Такой результат получился у меня, не считаю его удивительным, в силу использования Rust и микро-структуры RTS. Тот же Python имеет куда большую реализацию на C, к тому же не очень современную, поэтому результат оправдан. И вопрос не в том, кто лучше, а кроется куда глубже, как описывалось выше. В основном это разница в подходах программирования. Поэтому замечу, что эта тема не является предметом спора, вы можете проверить всё сами. К тому же многие заметят, что RTS на данный момент не имеет такое количество реализаций как на Python, он совсем молодой, но это уж точно не означает, что я не должен был провести тесты, и менять парадигму.

Документация и источники

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

Система версий

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

Что дальше?

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

Другие могут быть не согласны с этой мыслью, но для меня это так.

Автор: miruji

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js