Привет Хабр! Меня зовут Ильдар. Мне 29 лет. Программирую с 2003 года. За свою жизнь создал 4 фреймворка и язык программирования. В этом посте я поделюсь своим опытом, инсайтами, которые я получил при разработке языка программирования BAYRELL Language. Заранее прощу прощения за возможные синтаксические и пунктуационные ошибки в тексте и отсутствие картинок.


История создания


BAYRELL Language я начал создавать летом 2016 года. Тогда я узнал про ангуляр и реакт. Они произвели на меня впечатления прорывных технологий. Я тогда разрабатывал сайты на PHP и использовал Twig в качестве шаблонизатора. И хотел попробовать Angular. Начал искать, как можно прикрутить Angular к Twig.


В целом Twig+Angular работает, но был эффект моргания. Страница рендерилась через Twig на сервере, передавалась клиенту, и там уже рендерился Angular. И страница, пока рендерится у клиента, она моргала. И я тогда задумался, а есть ли клиент-серверный шаблонизатор, но такой, чтобы работал на PHP и JavaScript?


Я поискал в интернете, все рекомендовали использовать NodeJS в качестве сервера, либо V8 движок в PHP, либо микро сервисную архитектуру: backend на PHP, frontend на NodeJS. Только вот незадача. Я делал сайты на WordPress, Yii2. А на хостинге, не было поддержки ни NodeJS, ни V8. Тогда и пришла идея, написать кросс язычный шаблонизатор.


Я начал его делать. Написал за месяц на PHP, потом за другой месяц написал на JS. И быстро понял, что поддерживать два языка, одновременно сложно. Если появлялся, баг, то его нужно было пофиксить и там и там. Очень быстро версии шаблонизаторов стали разными, и расходились по функционалу. У меня было желание добавить Python. Я понял, что разрабатывать отдельно под каждый язык шаблонизатор, очень сложно. Тогда появилась другая идея. Найти транслятор языков, и сконвертировать код шаблонизатора на PHP в JS и в Python.


Я поискал в интернете про PHP to JS. Нашел несколько решений и опробовал их. Результат оказался дивный. Данные решение не полностью покрывали функционал PHP и JS. И как быстро выяснилось, массивы в PHP и JS отличаются. В JS это массивы, а в PHP это еще могут быть объекты. В JS console.log, а в PHP var_dump. Runtime среда, работа с массивами в языках отличаются друг от друга. И с этой проблемой, те решения, которые я рассматривал, не смогли ничего сделать.


Меня посетила следующая идея. Написать язык программирования, который будет транслировать код в несколько языков. Все равно большинство и так пользуются TypeScript, CoffeeScript, Dart, Babel, которые транслирует код в JavaScript. Почему бы не создать язык, который также бы транслировался, но не только в JS, но еще и в PHP, Python и т.п.


Так родилась идея создания BAYRELL Language.


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


За следующие полтора года, я переписал язык на функциональный стиль и добавил функциональное программирование. Оптимизировал транслятор, и откорректировал JS рендер. И теперь получилась рабочая альфа версия 0.8.3.


Возможности языка


  • Код на языке BAYRELL Language транслируется в PHP, NodeJS, JavaScript;
  • Встроенный в язык HTML шаблонизатор;
  • Рендер в браузере и на сервере;
  • Возможность разработки бэкенд api;
  • Функциональное программирование;
  • ООП;
  • Неизменяемые структуры данных;
  • Функциональные цепочки вызовов функций (pipe);
  • Асинхронное программирование async/await;
  • Кэширование результатов функций, через параметр memorize;
  • Лямбда цепочки;
  • Концепция обмена сообщениями между частями системы;

Более подробно о возможностях я распишу в документации.


BAYRELL Language содержит Runtime библиотеку, которая позволяет разрабатывать Full Stack программы. Начиная от запросов к базе данных, работа с API, заканчивая компонентами и рендером на клиенте.


Как начать пользоваться языком


Я постарался создать базовую документацию, и буду ее потихоньку дополнять. Проект находится в альфа стадии, и возможно функционал некоторых классов будет изменен в будущем.


Установка языка осуществляется следующей командой:


npm install -g bayrell-lang-compiler

Чистый проект:


git clone https://github.com/bayrell-tutorials/clear-project
cd clear-project
git submodule init
git submodule update

Трансляция проекта:


bayrell-lang-nodejs make_all
bayrell-lang-nodejs make_symlinks

Наблюдение изменений в проекте и автоматическая трансляция:


bayrell-lang-nodejs watch

Чтобы посмотреть проект, нужно в nginx прописать document root к папке web в проекте.


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




HTML Шаблонизатор


У шаблонизатора несколько отличительных особенностей:


  • Транслятор конвертирует код шаблона в PHP и JS.
  • Можно создавать свои компоненты и переиспользовать их в различных частях системы или в других проектах.
  • Direct патчинг HTML DOM вместо Virtual DOM.

Более подробное описание работы шаблонизатора, с примерами работы можно найти в документации.


Если вкратце, то создается модель данных LayoutModel, которая содержит всю информацию о странице (модель всего шаблона). Модель страницы – неизменяемая. Каждый компонент содержит чистую функцию render, которая отображает HTML код на основе модели. Модель может изменяться через JS события (нажатия кнопок мыши, клавиатуры и т.п.) путем создания новой модели с новыми свойствами. При возникновении события, компонент передает событие вышестоящему компоненту, о том, что его модель изменилась. Вышестоящий компонент, изменяет свою модель и передает сообщение выше, пока не дойдет до RenderDriver.


RenderDriver изменяет LayoutModel, и запускает перерисовку через requestAnimationFrame. Функция отрисовки использует функции render у компонентов, чтобы пропатчить HTML DOM. В функции render не создается VirtualDOM, а происходит прямой патч HTML.


Функциональное программирование


Сейчас тренд на функциональное программирование (ФП). Когда я разрабатывал язык, я понял, что такое функциональное программирование. Это, наверное, самый важный инсайт был для меня. Я понял, что ФП, это не Lisp, Haskell, F#, а принципы, которые применимы как к ФП, так и ООП языкам.


В своей практике я начал применять функциональный подход. И обнаружил, что исчезают некоторые шаблоны проектирования, такие как фабрики, контейнеры, Dependency injection, потому что, они становятся не нужны. Код становится чище, понятнее и его поведение легче предсказать. Уходит избыточность кода и файлов.


Например, у меня, после введения неизменяемых типов данных (структур), исчезла необходимость писать private у переменной, и создавать функции setName(), getName(). Обычно в ООП их всегда пишут. А в функциональном программировании они не нужны. Потому что структура неизменяемая.


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


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


Принципы функционального программирования


Данные принципы относятся к языку BAYRELL Language. В других ФП языках некоторых принципов может и не быть.


Первый принцип – это способ написания функций, которые можно запустить на разных серверах или потоках (масштабировать). При этом масштабирование не влияет на результат этих функций. Функции могут работать параллельно, а их работа не влияет друг на друга. Другими словами, масштабирование, это как кассы в супермаркете. Каждая касса определяется алгоритмом, если нагрузка увеличивается, открываются несколько дополнительных касс, если нагрузка низкая, уменьшается количество касс. Сами кассы идентичны и работают независимо друг от друга. Покупатель может подойти к одной из касс, и расплатиться за товары.


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


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


Третьим принципом функционального программирования, является возможность создания асинхронных функций. Что это значит? Пример, из жизни. Пусть нам надо написать письмо человеку с каким-то предложением, и узнать его ответ. А после его ответа принять решение. Но пока человек, отвечает, думает, он может отвечать, допустим сутки, в это время, мы можем заняться другими делами, пойти в кино, сверстать сайт, и т.п. Очень важно, что задачу выполняет один человек, но он делает «параллельно» эти задачи.


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


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


У неизменяемых структур данных три важных свойства:


  • Чтобы узнать изменились ли данные, или нет, достаточно сравнить ссылку на объект. Если ссылка изменилась, значит это новый объект, и данные изменены.
  • Их можно использовать в многопоточных системах, и не боятся, что какой-то поток, изменит объект, пока другой им пользуется. Можно обойтись без мьютексов.
  • Если передавать неизменяемые объекты в функции, то можно не бояться, что функции изменят значения объекта. Тем самым алгоритм становится значительно проще. И ошибок гораздо меньше.

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


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


Чистая функция – это:


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

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


Шестой принцип – каррирование и функции высших порядков. Функция высшего порядка – это функция, которая возвращает или принимает в качестве аргумента другую функцию. А каррирование, это метод создания функций высшего порядка. Обычно каждая вложенная функция принимает один аргумент. Но это не обязательное условие.


Пример на языке BAYRELL Language:


/**
 * Sum a + b
 */
lambda int sum(int a) => int (int b) use (a) => a + b;

или анонимная функция:


fn sum = int (int a) => int (int b) use (a) => a + b;

Функции высших порядков удобно применять в map, reduce, array_filter (php), цепочках pipe.


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


Например:


ls -la | grep .txt

Это pipe. Он выводит список файлов с расширением txt. Сначала вызывается функция, которая выводит листинг всех файлов, затем вызывается фильтр, который оставляет только txt.


В BAYRELL Language можно тоже создавать pipe. Пример:


User user = new User()
	|> User::setName('John')
	|> User::addEmail("john@example.com")
;

Или проще:


User user = new User()
	-> setName('John') 
	-> addEmail("john@example.com")
;

В данном случае создается пользователь, при вызове addEmail, первым аргументом передается объект пользователя и меняется имя, а вторая функция, добавляет email «john@example.com» и возвращает новый измененный объект. Сама передача объекта не указывается, поэтому функции содержат один аргумент, а не два. Это сделано для облегчения читаемости кода.


Pipe может быть асинхронным.


Более подробные примеры в документации.


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


Концепция обмена сообщениями


Сейчас в BAYRELL Language обмен сообщениями работает во Frontend: события JavaScript, нажатие мыши, события компонентов. И отправка Ajax запросов на Backend.


Пример отправки сообщения:


MessageRPC msg = await @->sendMessage
(
	new MessageRPC
	{
		"api_name": "App",
		"space_name": "ApiInterface",
		"method_name": "getDocument",
		"data":
		{
			"document": "/ru/" ~ prefix,
		},
	}
);
Document document = msg->isSuccess() ? msg.response : null;

К сожалению, REST Api не полностью может покрыть необходимый функционал. REST это всего лишь CRUD. А иногда, требуется запускать запросы, которые, например, компилируют какой-то модуль, загружают файл на сервер, или отправляют уведомления о событиях. И использовать методы HTTP (GET,POST,PUT,DELETE) для передачи команды запроса не очень хорошая идея.


Поэтому, при разработке концепции обмена сообщениями, была взята концепция D-BUS за основу. Есть объект, который получает сообщения. У него есть некий интерфейс, а у интерфейса метод.


Интерфейсы позволяют стандартизовать обращения. Например, CRUDInterface. Если объект обладает этим интерфейсом, то все веб компоненты, в которых реализован этот интерфейс, смогут отображать данные из этого объекта. И свободно обращаться в этому объекту по API.


Работу обмена сообщениями планируется в будущем сделать, через различный транспорт: WebSocket, DBus, RabbitMQ. Пока реализован транспорт через Ajax в качестве вызова API.


О языках


Я понял, во время разработки языка, что концепции языков С++, C#, Python, Java, PHP похожи. Отличается синтаксис, комьюнити и библиотеки, под эти языки.


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


  • Размер сообщества и наличие обилия библиотек. Писать все с нуля самому сложно. Я написал 4 фреймворка, и теперь понимаю, что порой лучше взять готовые решения.
  • Оценить будущие затраты на сопровождения и доработку кода, который вы написали.
  • Легко ли найти программистов под этот язык или фреймворк, и захотят ли они с ним работать. И сколько они будут просить зарплаты.
  • Сможете ли вы найти решение возникшего вопроса в интернете за несколько минут, касательно выбранной технологии?
  • Выдерживает ли этот язык или фреймворк нагрузку, которая предполагается при размещении в продакшн.

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


Будущие направления развития языка BAYRELL Language


То, зачем я начал разработку, это интеграция кода написанных на разных языках (Java, Python, C#, C++, NodeJS, JavaScript, Go, Lua). Или разработка библиотек под эти языки программирования. В рамках создания BAYRELL Language, я планирую максимально интегрировать его с этими языками. В идеале, я вообще задумываюсь, о свободной конвертации программ из одного языка в другой. Языки похожи, но они имеют свои различия и нюансы. Из-за этого транслятор из одного языка в другой создать сложно.


Я изучал данную проблему переиспользования исходного кода, в разных языках. Это называется Language Binding. В компилируемых языках есть dll, и все решается просто. Но появились интерпретируемые языки, и использование кода из одного языка другим, становится проблемой.


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


Еще одним направлением, является развитие FaaS системы и визуального программирования.


Это разработка мышкой функциональных программ в облаке. Я как программист очень много времени трачу на печатание кода, на постоянные сборки и компиляции. Хотя нужно тратить время на создание алгоритмов. Мне нравятся конструкторы сайтов, автоворонок продаж, чат ботов. Где можно мышкой потыкать, и получить готовую рабочую версию. Только зачастую, в некоторых конструкторах, нельзя писать кастомный код. Либо конструктор не дает выгрузить разработанный код. И еще в конструкторах нет Git.


Визуальное программирование сокращает время на создание программы. Так, к примеру, создать лэндинг на конструкторе можно за 2-3 дня. А если делать макет, дизайнить и верстать, то уйдет месяц, а то и больше.


Более подробно TODO List я написал в документации


Итог


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


По вопросам сотрудничества и вступления в сообщество пишите в ЛС.


Буду рад, если подпишитесь на меня в соц сетях.


Ссылки на соц сети доступны на странице.