Honey — я устал #2

9ae8a04317aab10412838ececab694e8

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

Описание проекта после перерождения

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

  1. Язык однозначно будет компилируемым. Скриптовые языки хороши, но для моих целей они не особо подходят. Так же, это сделает Honey альтернативой другим компилируемым языкам.

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

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

  4. Нет шаблонам! Хотя это частично пункт 2, но я решил его обособить, ведь шаблоны — это в первую очередь про zero-cost абстракции и универсальность кода. Но практика на С++ показала, что, не смотря на благие намерения такой фишки, она может нести за собой катастрофические последствия, начиная с повышенного порога входа и заканчивая boilerplate-кодом и прочими проблемами пункта 2.

  5. Отказ от полноценного ООП. Полиморфизм, наследование и прочие возможности ООП неоспоримо магия для программиста, что позволяет писать необычайно удобный в поддержке код. Но обилие функционала порождает собой проблемы пунктов 2 и 4. Истина познаётся в балансе:)

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

  7. Строгая типизация. Конверсии типов возможны только между целочисленными типами и из целочисленного в тип с плавающей запятой. иные преобразования нужно реализовывать явно в целях избежания неожиданных проблем

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

  9. Отсутствие рефлексии. Она усложняет как программу, так и её код в целом.

  10. Мощные вычисления во время компиляции. Всё, что можно посчитать или выполнить во время компиляции, будет посчитано или выполнено. Подробнее смотрите далее.

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

  12. Совместимость с уже существующими библиотеками. Встроенная поддержка библиотек на С.

  13. Независимость кода от среды. Где бы код на Honey не запустился, результат всегда будет ожидаемый.

  14. Стандартная библиотека. по умолчанию будет модуль libc, который полностью включает в себя биндинги к стандартной библиотеке С. Так же будет своя стандартная библиотека, опционально. она будет в модуле std.

Подробнее про сам язык

Синтаксис. Он будет подобен Rust ибо он весьма приятен глазу и интуитивен. То же касаемо именования встроенных типов. Вот пример функции main на Honey с использованием libc:

import libc;

fn main(args: str[]) -> i32 {
  puts("Hello, World!");
}

Определение типа возвращаемого значения опционально. Если оно отсутствует, по умолчанию применяется тип void. После всего, может идти несколько слов, обозначающих свойства функции. например:

fn pow2(n: i32) -> i32 nothrow noct { return n**2; }

Флаг nothrow логично названию обозначает, что внутри функции никаким образом не может произойти исключение. чаще это требуется только для упрощения процесса компиляции. Не рекомендовано к использованию, но вам никто не запретит. noct запрещает выполнение функции во время компиляции. Это бывает необходимо для ёмких функций, которые предпочтительнее выполнять во время работы программы.

Структуры

Структуры определяются весьма знакомым всем образом:

struct vec2 {
  float x; float y;
}

Заметьте: бесящая точка с запятой после этого не обязательна. Структура всегда является типом, то есть не нужно писать что-то из С на подобии typedef struct vec2_s vec2_t;. Но погодите:, а как же ООП? Я же сказал выше, что я не отказываюсь от него полностью, а лишь намеренно ограничиваю его возможности в семантике самого языка. Это правда. у структур могут быть методы. Методы бывают трёх типов: операторы, кон/деструкторы и обычные методы. Например, выше определенная структура может быть инициализированная как vec2{}, vec2 0, vec2 {x_val, y_val}. В чём разница:
Первый конструктор — это конструктор по умолчанию. он устанавливает все поля структуры в их изначальные значения. Второй — нуль-конструктор, он приравнивает все поля к нулю либо их нулевому состоянию. Третий конструктор — обычный, просто устанавливает полям их значения. Так же можно определить пользовательский конструктор, создав метод с названием ctor. Помимо конструкторов в языке так же есть инициализаторы. Они отличаются от конструкторов тем, что вызываются после выполнения конструктора по умолчанию, и может иметь самые разные назначения. для использования инициализатора нужно использовать синтаксис new vec2(args). Для определения инициализатора нужно создать метод с названием operator init. Так же в языке есть перегрузки операторов, которые ничем принципиально не отличаются от перегрузок из С++, кроме индексации структуры — для этого нужно два оператора operator setitem(isize index, T value) и operator getitem(isize index) -> T. Итоговый код структуры vec2 на языке Honey будет выглядеть так:

struct vec2 {
  float x; float y;

  // пользовательские конструкторы или инициализаторы
  // для этой структуры не требуются
  
  fn operator+(vec2 other) -> vec2 {
    return vec2{this.x + other.x, this.y + other.y};
  }
  // и все остальные операторы подобным образом

  fn operator getitem(isize i) -> float {
    switch i {
      case 0 { return this.x; }
      case 1 { return this.y; }
      default { throw OutOfBouds; }
    }
  }
}

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

Импорт файлов

В Honey нет заголовков. Компилятор многопроходный, так что нет необходимости создания прототипа для реализуемой в пределах кода Honey функции. Импорт работает, по сути, так же, как и inline из препроцессора С, но вместо текстовой подстановки модифицируется АСТ кода.

Циклы

Язык поддерживает следующие типы циклов:

  • С-подобный: for (init; cond; step) body

  • Итеративный: for iterator: iterable body

  • Вечный: for ever body // сделано намерено для дополнительных оптимизаций

  • С предусловием: while cond body

  • С постусловием: do body while cond

  • В целом тут всё и так понятно, только вот перед телом цикла можно указать название цикла (суть циклы являются именованными), поэтому конструкции break и continue могут содержать следующим словом название цикла, к которому оно относится.

Пример со сферической графической библиотекой в вакууме:

for ever mainloop {
  for event : getEvents() eventloop {
    if (event.type == QUIT) break mainloop;
  }
  clearWindow(); drawBox(...); flipBuffer();
  CapFPS(60);
}

Здесь наглядно видно, какое упрощение кода происходит. Кто-то скажет: «Это можно легко сделать метками и goto». Не стану обосновывать, почему это плохая практика.

Выполнение во время компиляции

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

import ct;

version: str = ct.loadFile("version.info");

Данный пример взят с потолка исключительно для демонстрации. На данный момент планируются следующие функции модуля ct:

  • readFile

  • writeFile

  • readLn

  • writeLn

  • osExec

  • jsonLoad

  • jsonSave

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

debug: bool = true;

if (debug) {
  fn log(msg: str) -> void { ... }
} else {
  fn log(msg: str) -> void { }
}

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

Преимущества и недостатки решения

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

Потребности программистов в повседневной работе и развлечениях

В первую очередь для бизнеса, программист — это производитель интеллектуального ресурса. Для выполнения своего функционала, он потребляет такой ресурс, как время, то есть преобразовывает время в интеллектуальный ресурс. Время важно в бизнесе по двум причинам: первая — самая очевидная — оно стоит денег. второе — оно важно в бизнес-стратегии. Итак, как же повысить КПД программиста? Упростить его работу, конечно же. Данный язык может взыскать популярность в проектах, связанных с низкоуровневым программированием, либо программированием высокоэффективного прикладного программного обеспечения ввиду своей специфики. Он предоставляет достаточно богатый уровень абстракций по сравнению с традиционным С, и при этом не перегружен, как С++ или Rust. Одним словом — золотая середина. Итак, основываясь на данные TIOBE Index, можно сделать вывод, что на данный момент процент использования низкоуровневых языков программирования составляет около 10,23%. Это физический максимум, который может охватить Honey как язык программирования низкого уровня. Но он, хоть и поддерживает низкоуровневые операции, исключительно низкоуровневым не является. Давайте посчитаем процент использования компилируемых в нативный код языков программирования в целом, результат, основываясь на всё тот же TIOBE Index будет 31.64%. Вот он — физический потолок охвата Honey в сказочно благоприятных условиях. Но зная, как тяжело убедить человека перейти с одного инструмента на другой, если ему уже «так удобно» или «так привычно» или «у меня уже десятки проектов на этом языке», то можно без зазрения совести рубить чисто на десять. Итого, предполагаемый результат охвата Honey, при условии, что о нём будут знать все программисты, использующие компилируемые языки программирования, равен примерно 3.16%. А теперь давайте поделим для честности это число ещё на два, ведь далеко не все программисты сразу узнают о его существовании, результат, ожидаемо, равен 1.58%. Каким бы малым числом результат не казался, это достаточно серьезный результат, ведь он на уровне Delphi/Rust. А Rust, не смотря на скромные 1.16%, на самом деле довольно популярный язык программирования с большой пользовательской базой.

Плюсы решения

  • Язык имеет весьма низкий порог входа.

  • Язык предоставляет массу zero-cost абстракций и тонну оптимизаций, не смотря на свою простоту.

  • Язык переносим и просто в понимании, код на нем редко будет болеть boilerplate’ом или проблемами с поддержкой и/или развитием.

Минусы решения

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

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

  • Спорные решения в реализации, например, отказ от полноценного ООП и рефлексии, может вызвать за собой лавину негатива.

Заключение

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

© Habrahabr.ru