Что это такое
Паттерн Interpreter вычисляет выражения небольшого языка: он строит дерево узлов-выражений — числа, сложения, вычитания — а затем обходит это дерево. Число вычисляется в само себя; сложение вычисляет обе свои стороны и складывает их.
В TypeScript дерево — это размеченное объединение: каждый узел является обычным объектом с полем kind, и одна функция выполняет переключение по этому полю. Поскольку объединение перечисляет все типы узлов, компилятор сообщит об ошибке, если какой-то случай пропущен.
Паттерн подходит для маленьких языков: фильтров, формул, простых правил. Настоящие языки заслуживают настоящих инструментов разбора.
Пример: крошечный калькулятор
Входная строка 3 + 4 - 2 превращается в дерево из узлов number, add и subtract. Один вызов interpret() на корне вычисляет всё выражение.
Одно дерево, одна функция interpret
parse строит дерево слева направо; interpret обходит его и печатает 3 + 4 - 2 = 5. Поддержка * потребовала бы одного нового члена объединения плюс одного нового case — существующие остаются нетронутыми.
Вывод: программа на TypeScript и захваченный вывод
// Каждый узел — обычный объект, помеченный полем kind.
type Expression =
| { kind: "number"; value: number }
| { kind: "add"; left: Expression; right: Expression }
| { kind: "subtract"; left: Expression; right: Expression }
function interpret(expr: Expression): number {
switch (expr.kind) {
case "number":
return expr.value
case "add":
return interpret(expr.left) + interpret(expr.right)
case "subtract":
return interpret(expr.left) - interpret(expr.right)
}
}
function parse(input: string): Expression {
const parts = input.split(" ")
let expr: Expression = { kind: "number", value: Number(parts[0]) }
for (let i = 1; i < parts.length; i += 2) {
const right: Expression = { kind: "number", value: Number(parts[i + 1]) }
expr = parts[i] === "+"
? { kind: "add", left: expr, right }
: { kind: "subtract", left: expr, right }
}
return expr
}
const input = "3 + 4 - 2"
console.log(input, "=", interpret(parse(input)))
Когда его использовать
Используйте, когда пользователи или конфигурации выражают логику на небольшом языке вашего собственного дизайна: фильтры поиска, формулы расчёта цен, правила проверки.
Не используйте, когда «язык» состоит из трёх фиксированных вариантов (достаточно обычного switch) — или когда это настоящий язык (возьмите генератор парсеров или готовый движок).
Плюсы и минусы
Плюсы: грамматика отображается один к одному на члены объединения; новые операции — это новые члены; деревья выражений можно строить, проверять и повторно использовать.
Минусы: с ростом грамматики становится болезненным; сам разбор всё равно приходится писать; производительность скромная по сравнению с скомпилированной логикой.