В исследовании языков программирования меня всегда наиболее привлекала их разработка.

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

Не надо писать такую многословную последовательность вызовов API, если требуется всего лишь вывести диалоговое окно пользователю видеоигры:

# пример кода для VN
character.draw("alice", character.LEFT, 0.1)
character.draw("bob", character.RIGHT, 0.1)
character.say("alice", "hello there!")
character.say("bob", "hi!")
character.state("alice", "sad")
character.say("alice", "did you hear the news?")

Напротив, DSL позволяет разработчикам сосредоточиться на  высокоуровневых деталях — то есть, в общем виде представить, как должен вестись диалог:

# Пример диалогового DSL (предметно-ориентированного языка)
  [ alice @ left in 0.1, bob @right in 0.1  ]
alice: hello there!
bob: hi!
alice[sad]: did you hear the news?...

Запрограммировав относящиеся к предметной области «эмпирические правила» в сам язык, можно в принципе исключить возможность создания некорректных программ, устранить когнитивную нагрузку и минимизировать зону поражения, через которую в программу проникают баги и эксплойты.

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

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

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

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

Проблема БЯМ или "на Python всё равно проще"

Начнём с той проблемы, привнесённой БЯМ, которая кажется мне наиболее серьёзной в контексте проектирования языков: за что бы мы ни брались, на Python всё равно будет проще.

Я немного преувеличиваю, но в данном случае хочу указать, что БЯМ значительно обходят по эффективности человека при работе с теми языками программирования, которые широко представлены в поданных им обучающих датасетах — я говорю о Python, JavaScript, TypeScript, т.д. 

Например, вот этот график взят из статьи 2024 года «Knowledge Transfer from High-Resource to Low-Resource Programming Languages for Code LLMs» (Перенос знаний из широко представленных в слабо представленные языки программирования применительно к БЯМ, обученным для этой цели). В нём показана успешность одной большой языковой модели (StarCoderBase-15B) при решении задач на программирование пропорционально тому объёму тренировочных данных, которые содержатся в написанных на этом языке файлах.

В статье предложен метод повышения производительности для этих «слабо представленных» языков путём генерации синтетических данных. Таким образом, те линии на графике, что идут вверх, отражают прогресс после тонкой настройки с использованием этих данных. Но такие преобразования применялись только в сравнительно небольших моделях, которые, конечно же, не сравнятся по масштабу с такими гигантами, как ChatGPT, CoPilot, Gemini, Claude и т.д., которые сегодня пользуются наибольшей популярностью.

Если рассмотреть исходные точки в этих данных, то получается бледная картинка: по мере того, как мы принимаем во внимание всё более нишевые и специфичные языки, производительность этих моделей в какой-то момент уходит в отвесное пикирование. Причём, учтите, что даже эти «слабо представленные» языки по-своему самодостаточны и находят применение в отраслевых системах, которыми пользуются миллионы человек. Если написанного на языке кода не так много, чтобы качественно обучить на нём БЯМ, это ещё ничего не говорит о самом языке.  

Если производительность БЯМ так резко падает даже при работе с этими языками общего назначения, то чего же ожидать от БЯМ, которая попробует писать код на предметно-ориентированном языке?

Внезапно издержки альтернативных возможностей при работе с DSL разом удвоились: там, где доминируют БЯМ, при желании создать DSL требуется вкладываться в проектирование и построение как самого языка, так и его инструментария. Более того, сами пользователи должны отказаться от помощи БЯМ, так как в начале пути БЯМ не в силах сгенерировать для вашего предметно-ориентированного языка хоть какой-нибудь код.

Вот здесь я начинаю по-настоящему бояться того, что нас ждёт: стагнация DSL? Станет ли кто-либо утруждать себя написанием DSL, если, работая на нишевом языке, ты вынужден полностью отказаться от БЯМ? Либо же порог входа в проектирование DSL попросту резко вырос, но где же тогда мотивация для авторов прилагать усилия к проектированию DSL, если, создав новый язык, ты теряешь всякое подспорье со стороны больших языковых моделей?

Какие намечаются направления в проектировании языков в условиях доминирования БЯМ?

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

Пока вырисовываются три интересных направления, развитие которых я ожидаю увидеть в ближайшем будущем, но, если вы усматриваете и другие – расскажите, мне интересно!

Направление 1: рассказываем большим языковым моделям о DSL (на Python?)

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

Так что… давайте попробуем предоставить БЯМ такой контекст?

В свежих научных работах, например, в Verified Code Transpilation with LLMs (2024) (Верифицированная транспиляция кода с использованием БЯМ) наблюдается такой тренд: исследователи всё успешнее пишут выражения на нишевых языках (именно в этой статье речь идёт о логических инвариантах). Для этого они просят LLM сгенерировать выражения на хорошо известном языке (в данном случае, на Python), после чего вручную переводят полученное на малоизвестный интересующий их язык.

В вышеупомянутой статье авторы предлагают при помощи БЯМ автоматически транспилировать  код для тензорной обработки на различные DSL. Чтобы гарантировать корректность такого кода, от БЯМ также требуют предложить инварианты, на основе которых можно доказать эквивалентность обеих программ:

# Пример инварианта из статьи Verified Code Transpilation with LLMs
def invariant_outer(row, col, b, a, out):
   return row >= 0 and row <= len(b) and
      out == matrix_scalar_sub(255, matrix_add(b[:i], a[:i])

Ключевой приём авторов этой статьи — использование Python в качестве языка-посредника. Они просят БЯМ сначала сгенерировать на Python такой код, который выдаётся ограниченными подмножествами, а затем пишут такие программы, которые «повышают» эти выражения на Python до выбранных авторами DSL. Таким образом, авторы, фактически, генерируют в БЯМ код для «заказных» DSL, но для этого не приходится ни тратиться на дорогостоящую ступенчатую тонкую настройку, ни переучивать модель.

Если обобщить эту идею и постараться шире рассмотреть проблему проектирования языков, то можно сформулировать вопрос так: а можно ли автоматизировать такой перевод с языка на язык? Например, можно ли создать фреймворки проектирования DSL, в которых также предоставлялись бы понятные для БЯМ описания семантики этих языков, сформулированные на Python? Может быть, удалось бы создать такие фреймворки, которые проверяли бы эквивалентность поведения этих выкладок на Python и моделируемого на их базе предметно-ориентированного кода? Наконец, можно ли автоматически генерировать описания на Python, беря за основу саму реализацию интересующего нас DSL?   

Направление 2: как БЯМ помогают состыковать формальную и неформальную составляющую DSL

Можно подступиться к проблеме и с другой стороны. Я вижу, что на пересечении DSL и БЯМ можно исследовать новые возможности проектирования таких DSL, потоки задач в которых строятся по одним шаблонам с программированием через БЯМ.

Здесь сделаю небольшое лирическое отступление и расскажу, как пользуюсь БЯМ в моей собственной практике, а также как они повлияли на то, как я пишу определённые виды кода — а именно, скрипты.

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

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

Вот промпт, который я недавно сформулировала, чтобы сгенерировать код на Python для некоторого базового анализа данных:

  • Напиши чрезвычайно параллельный скрипт, перебирающий все твои файлы, лежащие в afp-versions, выполняющий функцию split_file, которой ты передаёшь текстовое содержимое файла. В ответ код возвращает df для pandas, в котором учитываются столбцы namestart_lineend_linefirst_word

  • Для каждого файла вида запись: версия (имя подкаталога, расположенного прямо уровнем ниже того каталога, afp-versions в котором мы находимся) и проект (твои файлы будут находиться в подкаталоге после указания версии, то есть, проект находится в подкаталоге ровно на уровень ниже него. (расширь этими параметрами все строки в df, возвращённом от split_file )

  • Итеративно шаг за шагом объедини все эти файлы в один фрейм данных, а после перезапуска демонстрируй прогресс при помощи tqdm. Параллелизм организуй при помощи пула потоков.

Вышеприведённое объяснение в основном объясняет, для чего именно предназначен этот код, и БЯМ сгенерировала для меня код, делавший как раз то, что мне требовалось.

Теперь самое интересное замечание об этом фрагменте кода с точки зрения проектирования языков: дело в том, что БЯМ генерирует «неполную» программу. В частности, даже не просите БЯМ сгенерировать функцию split_file. Вместо этого дайте ей спецификацию этой функции, опишите, что именно эта функция должна получать на вход и выдавать на выход, насколько важны будут эти значения на оставшихся этапах выполнения задачи.

В широком смысле, когда я прибегаю к помощи БЯМ, готовя такие импровизированные одноразовые скрипты, я в общих чертах обрисовываю ей, что именно собираюсь сделать, приказываю БЯМ сгенерировать «склеивающий» код, а затем вручную реализую «интересную» часть задачи сама.

Возвращаясь к тому, как это выглядит с точки зрения проектирования языков, вот какой вопрос возникает у меня после всего проделанного выше: как бы мне внедрить подобные потоки задач в DSL? А именно, как перекинуть мостик между формальной и неформальной составляющей? Тот код, который я пишу вручную — это «формальная» часть проекта, а мой текстовый промпт – «неформальная». В рассматриваемом здесь фрагменте кода я выражаю формальную часть как спецификацию, а неформальную — в виде обычного текста. Можно ли это автоматизировать? Можно ли писать такие DSL, которые смогут бесшовно интегрироваться с неформальным текстом? Может быть, получится автоматически генерировать на естественном языке спецификации, основанные на типах в составе DSL или на том анализе, который выполняет сам DSL?

Направление 3: Проектирование языков для верифицированного БЯМ-синтеза

Пожалуй, именно это направление исследуется активнее всего из всех, затронутых в этом посте. Однако, ещё одна интересная идея в области проектирования языков — на волне развития БЯМ перейти к проектированию языков для составления спецификаций.

Итак, в общем виде можно сказать, что пока БЯМ раскочегариваются, сформировалась целая кустарная исследовательская индустрия, представители которой принялись выяснять, можно ли верифицировать вывод БЯМ (код, сгенерированный большими языковыми моделями) при помощи специализированных верификационных языков, таких как Dafny или Boogie. Первая попавшаяся мне статья такого рода вышла в 2024 году и называется «Towards AI-Assisted Synthesis of Verified Dafny Methods» (Перспективы синтеза верифицированных спецификаций на Dafny с использованием ИИ). Впрочем, я уверена, что таких статей уже гораздо, гораздо больше, и некоторые из них опубликованы, а часть пока находится в работе.

// Пример из Towards AI-Assisted Synthesis of Verified Dafny Methods
method FindSmallest(s: array<int>) returns (min: int)
  requires s.Length > 0
  ensures forall i :: 0 <= i < s.Length ==> min <= s[i]
  ensures exists i :: 0 <= i < s.Length && min == s[i] {
 ...
}

Не будем просить модель сгенерировать какой-то код, поскольку он может получиться сложным, запутанным и содержать баги. Вместо этого, полагают авторы, лучше просить модель создавать код на верифицированных языках, например, Dafny, имея на выходе спецификации. Таким образом, пользователь сможет взглянуть на спецификацию, понять, что делает программа — и, только если проверка окажется не пройдена, ему придётся разбираться в коде, сгенерированном БЯМ. Разумеется, проверка этих программ — тоже непростая задача, которая может дополнительно осложняться, например, медленными или хрупкими доказательствами. К счастью, над решением этой проблемы уже работают — я рассказала об этом здесь

С точки зрения проектирования языков здесь интересно задуматься, как мы могли бы а) интегрировать эти спецификации в DSL и b) как лучше проектировать наши верификационные DSL, чтобы схватывать в них именно те свойства конкретной предметной области, которые нас интересуют. Разумеется, при построении DSL для диалога будут важны совсем не те свойства, что при построении языка, скажем, для маршрутизации пакетов. Можно ли автоматически составлять языки спецификаций на основе реализации нашего предметно-ориентированного языка?  

Заключение. Как БЯМ проектируют языки

Итак, я считаю, что БЯМ подкинули проектировщикам DSL интересную проблему — теперь издержки альтернативных возможностей при использовании нишевых языков существенно возросли, поэтому проектировщикам языков приходится держать высокую планку, чтобы вообще работа с DSL оставалась целесообразной. В то же время, пространство возможностей радикально расширилось, и перед нами открылось несколько новых путей развития, определённо заслуживающих исследования.

Главный вывод — проектирование языков, и предметно-ориентированных языков в частности, должно адаптироваться к реалиям нынешнего безумного мира. Если мы не будем осторожны, то область проектирования новых языков вполне может впасть в стагнацию, мы потеряем разнообразие нынешних прикольных и интересных DSL, и всем придётся писать на одном сплошном Python…

Комментарии (4)


  1. FarhodN
    05.07.2025 05:21

    И что мы должны иметь в итоге?


  1. Dhwtj
    05.07.2025 05:21

    В промпте жесткая зависимость от библиотек питон. Результат немного предсказуем


  1. Octagon77
    05.07.2025 05:21

    А чего было ожидать? По карме, нужно хорошенько расплатиться за бездумное размножение языков.

    DSL всегда конкурировали с кодогенерацией - тем же DSL, вид сбоку. БЯМ не помогает DSL и помогает кодогенератору. И лучшим и единственным DSL будет английский.

    Кроме того, разбираясь что за ерунда вышла, в обоих случаях можно почитать код, только для DSL он на ассемблере, а для кодогенератора - на основном языке.

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


  1. ValeriyPus
    05.07.2025 05:21

    (del)