Предметно-ориентированные языки, когда они используются эффективно, поднимают уровень абстракции над программными идиомами и паттернами проектирования. Они позволяют разработчику выразить свои намерения на соответствующем уровне абстракции.
Многие программисты считают создание собственного языка программирования чем-то типа вершины возможного творчества. Но не потому что это требует насмотренности и развитого дизайнерского чутья, а потому что это просто технически сложная задача, требующая многих часов работы редких по интеллекту людей. Но теперь, кажется, это больше не так.
У меня самого нет никакого бэкграунда в области разработки языков. Я понимаю общие принципы, что-то немножко читал на тему, делал несколько проектов в рамках учебы (1, 2), ну и на этом почти что все. Но на днях мне потребовалось спроектировать микро-язык для машиночитаемых комментариев, чтобы потом встроить его в более крупную систему, которую я создаю. Я решил сделать этот язык синтаксическим подмножеством Python и в итоге довольно быстро слепил готовое решение, используя готовый парсер Python. Результатом своей работы я решил поделиться в одном из питонячьих чатиков и там у меня вполне логично спросили: коли уж я предлагаю новый язык, где собственно грамматика для этого языка?
Для тех, кто не в контексте: в современных языках программирования сегодня не принято писать парсеры исходного текста программы “руками”, такие программы получаются слишком сложными и запутанными. Вместо этого правила парсинга описывают в форме специального набора директив, который называют BNF. Файл с BNF скармливают специальной программе, которую называют «генератором парсеров». И уже он порождает собственно парсер, то есть другую программу, которая разбирает текст исходной программы на языке, который вы хотите создать. Так вот, изначально мне не нужно было писать свой BNF, потому что я воспользовался готовым парсером Python и объявил свой микро-язык подмножеством синтаксиса Python. Но вещь, о которой я почему-то не подумал — это «а как быть с людьми, которые захотят написать для него парсер на другом языке, где нет встроенного парсера Python?». Очевидно, все же придется писать свой BNF.
Раньше я не писал полноценных языков, и соответственно не имел дела с BNF. Но в итоге на решение этой задачи у меня ушло, наверное, не больше 10 минут. Как?
LLM
Я зашел в чат с LLM и описал по пунктам все правила, по которым работает мой мини-язык, прямо в виде обычного текста на русском:
Существуют 2 вида выражений: простые и сложные
Простое выражение состоит из ключа и действия, разделенных двуеточием
Между ними также может быть любое количество пробелов, они игнорируются
Ключ и действие могут быть только разрешенными python-идентификаторами, т.е. валидными названиями для переменных
Пример простого выражения:
key: actionСложное выражение отличается от простого тем, что тут расширяется синтаксис действия, к нему можно добавлять аргументы через запятую в квадратных скобках
Пример сложного выражения:
key: action[arg1, arg2]Допустимо любое положительное количество аргументов, но использовать просто квадратные скобки без аргументов нельзя
Между аргументами может быть любое количество пробелов
Аргументами могут быть: валидный python-идентификатор, два валидных python-идентификатора через дефис (или через минус; пробелы между ними игнорируются), строки в одинарных или двойных кавычках (с поддержкой экранирования и прочего дополнительного синтаксиса, но без поддержки модификаторов типа
fилиt) обычные числа, числа с плавающей запятой, комплексные числа (например,5j), константыNone,True,False, а также...(ellipsis).Несколько таких стейтментов могут перечисляться через символ
#, с любым количеством пробелов между ними, это тоже должно быть включено в грамматику. Все, что не подходит под определение стейтмента между двумя символами#или между началом строки и#, или между#и концом строки, должно просто игнорироваться.
Нейронка, конечно, начала с того, что предложила некоторый готовый питонячий код, содержащий несколько регулярок. Это ожидаемо, ведь большинство людей в интернете предпочтут регулярку полноценному парсеру, а значит в обучающей выборке текстов у ИИ было гораздо больше их, чем полноценных парсеров. Но я знал об ограничениях регулярных выражений в парсинге рекурсивных грамматик, поэтому меня это не устраивало. Я прямо попросил написать полноценный BNF, и она это сделала. Но как мне проверить, что результат получился правильный? Я поступил так:
Спросил в отдельном чате, какие сейчас существуют лучшие генераторы парсеров на Python, и получил список, в котором на первом месте была библиотека
lark. Затем я попросил нейронку написать наlarkготовый парсер для моего мини-языка, используя адаптированный подlarkBNF, который получил на предыдущем шаге. В итоге LLM сгенерила около 200 строчек кода, которые уже дальше легко покрыть тестами (тоже с помощью ИИ), быстро пробежаться глазами по ассертам и убедиться, что все работает как надо.Скормил сгенерированный нейронкой BNF ей же в отдельном чате и попросил накидать примеров валидного синтаксиса на этом языке. Убедился, что он совпадает с тем, который я описывал изначально.
Что в итоге? В итоге в документации metacode есть готовый BNF, на основе которого кто угодно на каком угодно языке может сгенерировать совместимый парсер, выглядит он вот так:
line ::= element { "#" element }
element ::= statement | ignored_content
statement ::= key ":" action [ "[" arguments "]" ]
ignored_content ::= ? any sequence of characters excluding "#" ?
key ::= identifier
action ::= identifier { "-" identifier }
arguments ::= argument { "," argument }
argument ::= hyphenated_identifier
| identifier
| string_literal
| complex_literal
| number_literal
| "True" | "False" | "None" | "..."
hyphenated_identifier ::= identifier "-" identifier
identifier ::= ? python-style identifier ?
string_literal ::= ? python-style string ?
number_literal ::= ? python-style number ?
complex_literal ::= ? python-style complex number ?
На всю работу у меня ушло около 10 минут, при этом я более-менее уверен, что парсер написан правильно, т. к. перекрестно проверил это двумя разными способами. Но важнее тут не этот конкретный результат, а общие выводы, к которым я пришел.
Выводы
Software 1.0 easily automates what you can specify. Software 2.0 easily automates what you can verify.
Меня очень вдохновляет мысль о том, что мы могли бы просто декларативно, на высоком уровне, указать свойства, которыми должен обладать тот или иной фрагмент кода, а затем реализовать это с помощью вайбкодинга и доказать, что оно соответствует спецификации.
Мартин Клепманн у себя в блоге
Этот пример работы с LLM, кажется — хороший пример того, что ждет всю индустрию создания ПО в ближайшие годы. Вот несколько хайлайтов:
Эффективная работа с ИИ требует от юзера широкого кругозора и кучи базовых навыков, начиная с владения как минимум 1 языком программирования, кончая написанием тестов. Я четко понимал, чем хороший парсер отличается от набора регулярок, и почему последнее меня не устраивает. Я немного интересовался созданием языков программирования в прошлом и знал, что такое
BNF, даже если никогда не приходилось использовать это на практике.Задача написания мини-языка относится к легко валидируемым, а значит легко автоматизируется нейронками. Концептуальная работа по изобретательству все равно осталась на мне, потому что как раз ее валидировать довольно сложно. А вот корректность самого парсера валидировать тривиально. Это значит, что у людей, которые хотят создавать свои DSL, но не хотят много возиться со сложной машинерией вокруг этого, наконец-то развязаны руки.
Изменился общий баланс того, что мы считаем сложным, а что нет. Создание своих DSL теперь тривиально и мы можем ожидать, что их станет сильно больше в ближайшие годы. А вот навыки типа общей насмотренности и понимания принципов того, как устроены вещи, пусть и без обширных деталей, а также критического мышления — только вырастут в цене, потому что наконец-то приобрели мощный рычаг.
Эта статья также опубликована у меня на сайте. Не все, что я пишу, публикуется на хабре, так что добро пожаловать.