Главная / Статьи / Почему теория компиляторов всё ещё важна, когда код за вас пишет ИИ

Почему теория компиляторов всё ещё важна, когда код за вас пишет ИИ

ИИ сгенерирует парсер за минуты, но не скажет, когда он вам нужен. Как маленькие языки и детерминированные правила побеждают промпты и почему узкое место — это вы.

1253 слов

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

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

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

Контраргумент такой: написать компилятор C или JavaScript — одно, а всё, что позволяет делать теория компиляторов, — совсем другое. Считать её полезной только для создания языков программирования значит недооценивать её. Те же идеи лежат в основе серьёзной обработки текста, а ещё делают код проще и понятнее.

Теперь ИИ дал скептикам новые аргументы. Модель знает всю теорию и сама сгенерирует код парсинга, так зачем что-то учить? Это настоящий аргумент или просто отговорка?

Применения, о которых никто не спрашивает

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

test('Simple addition', () => {
  const calculator = new Calculator();
  calculator.press(4);
  calculator.addition();
  calculator.press(2);
  calculator.equals();
  expect(calculator.getDisplay()).equals('6');
});

По отдельности читается нормально, но следить за этим утомительно. Умножьте на сорок, и вы перестанете понимать, что покрыто, а что нет. Немного «компиляторного» мышления, и можно написать один хелпер, который изображает пользователя и всё проверяет, так что тест сжимается до одной строки:

test('Simple addition', () => {
  runCase('4 + 2 = [6]');
});

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

test.each([
  '[0]                # display starts at zero',
  '1 [1]              # pressing a digit shows it',
  '12 [12]            # digits concatenate',
  '4 + 2 = [6]        # a basic sum',
  '5 × 3 = [15]       # product works the same',
  '7 - 9 = [-2]       # negative results',
  '9 ÷ 0 = [Error]    # division by zero errors',
  '2 + 3 × 4 = [14]   # respects precedence',
])('%s', runCase);

Часть случаев здесь опущена, но разница видна. Представьте, что вам нужно отревьюить такое же покрытие, расписанное случай за случаем.

Ещё одно место, где это окупается, — связывание зависимостей между модулями. В коде вы бы написали что-то вроде этого:

const system = new System();
system.createModule('A');
system.createModule('B');
system.createModule('C');
system.createModule('D');
system.connect('A', 'B');
system.connect('A', 'C');
system.connect('B', 'C');
system.connect('C', 'D');

Немного проектирования языка, и вместо этого вы напишете вот что:

const system = compileSystem(`
  A -> B -> C -> D
  A      -> C
`);

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

Примеров ещё много, и иногда компилятор даже писать не нужно, потому что он уже существует. Многие конфигурации вполне можно держать в YAML. Система сущностей и компонентов, распространённая в играх, легко растёт и адаптируется, когда сущности описаны так:

goblin:
  extends: enemy
  health: 30
  damage: 5
  ai: aggressive
  drops: [gold, dagger]
  resistances: [poison]

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

ИИ делает это ещё нужнее

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

В итоге вы работаете между двумя совсем разными видами языка. Естественный язык — то, как вы разговариваете с ИИ. Код — то, чем вы ИИ управляете или даже помогаете ему. Если код нужно изменить, вы либо просите ИИ сделать это, либо разбираетесь в том, что он написал.

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

Scenario: User withdraws cash from ATM
  Given john's balance is $200
  When john withdraws $100 from the ATM
  Then john's balance should be $100

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

Считайте это третьим языком: не код и не естественный язык, но детерминированный. Он может выглядеть так:

rule "refund window":
  when order.status is "delivered"
  and days_since(order.delivered_at) <= 30
  then allow refund
  else deny "refund window expired"

guardrail "no medical advice":
  if response mentions [diagnosis, dosage, prescription]
  then block and escalate to human

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

Узкое место — вы

Это возвращает нас к главному тезису. Сколько из этих вариантов модель предлагает по собственной инициативе? Обычно ни одного.

У ИИ поразительные возможности и богатая теоретическая база, и ничто из этого не помогает, если вы не знаете, как это применить. Странная ситуация. Даже если модель знает теорию, вы не сможете ею воспользоваться, пока не знаете о её существовании.

Хорошая новость в том, что вся теория вам больше не нужна. Достаточно знать, что возможно и чем это может помочь. Навык сместился от умения что-то сделать к умению понять, что попросить. Когда вы это знаете, вам доступно всё, что знает модель.

На расстоянии одного промпта

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

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