Своя поверхность: как написать тот же язык по-французски
Обычный язык программирования пишут по-английски, и это не решение, а привычка,
которую никто не пересматривал. if, while, return — английские слова, и
человек, для которого английский не родной, каждый день читает код на языке, на
котором не думает.
flang устроен иначе: слова языка живут в таблице, а не в грамматике. Сегодня таблица знает четыре набора слов — русский, английский, эсперанто и китайский, — и все четыре равноправны: компилятор не спрашивает, на каком языке написан файл, он просто узнаёт слова.
Эта глава — про то, как это сделано и как добавить пятый.
Одна программа, три записи, одно дерево
В репозитории лежит факториал, записанный трижды: examples/rosetta/factorial.flang,
examples/surfaces/factorial.eo.flang, examples/surfaces/factorial.zh.flang.
Вот одна и та же функция на трёх поверхностях:
тотальная функция «Произведение»
принимает элементы: список числа
возвращает число
пример «Произведение четырёх»
дано элементы равно [1, 2, 3, 4]
ожидается 24
свёртка элементы начиная с 1 как акк и эл → акк умножить на эл
totala funkcio «Produto»
akceptas eroj: listo de nombro
redonas nombro
ekzemplo «Produto de kvar»
donite eroj egalas [1, 2, 3, 4]
atendata 24
faldo eroj komencante per 1 kiel akumulo kaj elem → akumulo fojoj elem
完全 函数 «乘积»
接受 诸项:列表 数字
返回 数字
示例 «四数之积»
给定 诸项 等于 [1,2,3,4]
期望 24
折叠 诸项 起始于 1 作为 累加 和 当项 → 累加 乘以 当项
Это не «перевод документации» и не три похожих файла. Проверяется утверждение так — командой, которую можно повторить:
$ flang check examples/rosetta/factorial.flang
$ flang check examples/surfaces/factorial.eo.flang
$ flang check examples/surfaces/factorial.zh.flang
Все три отвечают «проверено — разбор, типы, завершаемость, ядро и примеры;
замечаний нет». А flang ast показывает, почему: деревья разбора совпадают по
форме — пять функций, одинаковая структура узлов, одинаковые типы и примеры.
Отличаются только имена, которые написал автор. Замер сделан на выпуске 0.7.3
сравнением вывода flang ast у всех трёх файлов.
Отсюда практическое следствие: программу, написанную по-китайски, читает и печатает в C тот же компилятор, что и русскую. Поверхность не влияет ни на доказательство завершения, ни на печать в целевые языки — она кончается на лексере.
Где живут слова
Таблица — в flang/self/lexer.flang, то есть в самом компиляторе, написанном на
самом языке. Две формы записи, обе строками:
|тотальная:total|total:total|totala:total|完全:total|
|total@ru:тотальная|total@en:total|total@eo:totala|total@zh:完全|
Первая отвечает на вопрос «какое понятие стоит за этим словом» — ею работает разбор. Вторая — обратная: «каким словом назвать это понятие на такой-то поверхности», и ею работает диагностика, чтобы цитировать ошибку словами того языка, на котором человек писал файл.
Понятий в таблице 150 (счёт: все пары понятие@поверхность в
flang/self/lexer.flang на 0.7.3). Открыты они так:
| поверхность | понятий |
|---|---|
| русская | 150 |
| английская | 149 |
| китайская | 138 |
| эсперанто | 134 |
На всех четырёх открыто 133 понятия. У семнадцати слова где-то нет — и это не недоделка, а записанное решение.
Поверхность не обязана быть полной
Самый поучительный пример — эсперанто и слово модуль.
В эсперанто «modulo» называет и программный модуль, и остаток от деления. Второе
значение в таблице уже занято: modulo отдано понятию «остаток от». Одно слово
не вправе называть две конструкции — значит эсперантская поверхность здесь
молчит, и файл factorial.eo.flang пишет заголовок английским словом module:
module «Faktorialo»
Автор примера объясняет это прямо в комментарии к файлу: «выбор термина принадлежит владельцу языка, а не переводчику».
Вот все семнадцать понятий, у которых на какой-то поверхности слова нет:
| понятие | по-русски | нет на |
|---|---|---|
alias |
это |
en, eo, zh |
ensures |
обеспечивает |
eo, zh |
requires |
требует |
eo, zh |
forall |
для всех |
eo, zh |
induction |
индукция по |
eo, zh |
claim |
утверждаем |
eo, zh |
qed |
следовательно доказано |
eo, zh |
byExample, byHypothesis, byProperty |
по примеру, по предположению, по свойству |
eo, zh |
decreases |
убывает |
eo, zh |
module |
модуль |
eo |
not |
не |
eo |
nested |
вложен объект |
eo |
embedding |
вложение |
eo |
supervision |
надзор |
eo |
opPercent |
процентов |
zh |
Видно закономерность: не открыт прежде всего слой доказательств — на
эсперанто и китайском ещё не выбраны слова для обеспечивает, требует,
индукция по. Файл на такой поверхности пишет недостающее понятие словом другой
поверхности, и компилятор это принимает: смешивать слова разрешено, потому что
понятие одно.
Что придётся решить, а не перевести
Поверхность — это не словарь, и главная работа в ней не переводческая.
Слово должно быть одно на понятие. История с modulo — не исключение, а
типичный случай: в любом естественном языке найдётся слово, которое хочет
назвать две разные вещи. Решает это владелец языка, а не переводчик, и решение
записывается в таблицу вместе с причиной.
Слово должно быть отделимо. У китайской поверхности пробелов внутри фразы
нет, и это разбирается отдельным правилом: 函数长度 — одно имя, а 函数 长度 —
два слова. Поэтому между ключевыми словами китайской поверхности пробел
обязателен, ровно как между тотальная и функция. Полноширинные : , (
) при этом делят поток наравне с обычными и значат то же самое.
Буквы должны быть известны лексеру. Надстрочные буквы эсперанто (Ĉ Ĝ Ĥ Ĵ Ŝ Ŭ) стоят в таблице заглавных отдельными парами: без них Se в начале строки не
свелось бы к se, и ключевое слово не нашлось бы. Для французского то же самое
касается É È Ê À Ç Ô Û — их надо добавить в ту же таблицу, иначе
Élément в начале строки не совпадёт с élément.
Как добавить французскую поверхность
Работа делится на четыре шага, и первый — не про код.
1. Выбрать слова. 150 понятий, из них хотя бы 133 — те, что открыты на всех
поверхностях сегодня. Для каждого нужно французское слово, которое не занято
другим понятием: fonction, accepte, retourne, exemple, étant donné,
attendu, si, alors, sinon. Слова из нескольких слов допустимы — étant donné не хуже, чем русское вложен объект.
2. Дописать таблицу. Обе формы в flang/self/lexer.flang: прямую
(fonction:function) и обратную (function@fr:fonction). Компилятор написан на
самом языке, поэтому правка — это правка программы на flang, и она проходит те
же проверки, что любая другая.
3. Добавить буквы. Диакритика французского — в таблицу заглавных, рядом с эсперантской.
4. Написать пример и сверить деревья. examples/surfaces/factorial.fr.flang,
слово в слово с русским оригиналом. Доказательство, что поверхность вышла
правильной, — не чтение, а сравнение:
$ flang check examples/surfaces/factorial.fr.flang
$ flang ast examples/surfaces/factorial.fr.flang > fr.json
$ flang ast examples/rosetta/factorial.flang > ru.json
# деревья обязаны совпасть по форме: те же функции, узлы, типы и примеры
Если форма разошлась — где-то слово попало не на то понятие, и это видно сразу, а не через полгода на чужом коде.
Чего поверхность не даёт
Честная граница, чтобы не ждать лишнего.
Поверхность меняет слова языка, а не имена в вашей программе: «Рассчитать скидку» останется тем, что вы написали, и печать в целевой язык оставит его как
есть. Она не переводит стандартную библиотеку: имена её функций — тоже написанные
кем-то имена. И она не делает язык понятнее сама по себе — если правило запутано,
оно будет запутанным на любом наборе слов.
Зато она снимает ровно одну вещь, которую обычно принимают как данность: чтобы писать проверяемые правила своей предметной области, не обязательно делать это словами чужого языка.