Стандартная библиотека и связывание модулей
Каталог flang/stdlib/ — пять файлов, около 1160 строк на самом flang. Читать
их полезно не столько ради функций, сколько ради комментариев: каждый модуль
открывается объяснением, какое ограничение языка он обходит и какой ценой.
| Файл | Модуль | Строк | О чём |
|---|---|---|---|
lists.flang |
«Списки» | 373 | длина, обращение, соединение, срез, поиск, свёртки, сортировка вставками, уникальные |
strings.flang |
«Строки» | 308 | разбиение, замена, регистр, префикс и суффикс, палиндром, обрезка пробелов |
numbers.flang |
«Числа» | 212 | знак, модуль, минимум и максимум, целочисленное деление, НОД, степень, факториал, цифры |
optional.flang |
«Опциональное значение» | 126 | сумма типов «Есть число» / «Нет числа» |
result.flang |
«Результат» | 140 | сумма типов «Успех» / «Ошибка», безопасное деление |
Три границы, видные в комментариях
Стандартная библиотека здесь работает как карта ограничений языка. Каждый файл упирается в своё.
Списки — в отсутствие полиморфизма. lists.flang — это библиотека для
список числа, и в шапке объяснено почему: типы не параметрические, функции не
значения, поэтому «Сортировать» для строк — отдельная функция с отдельным телом.
Приписано честно: «для списка строки те же приёмы повторяются дословно».
Отдельная мелочь, из которой вырос порядок функций в файле: приписать элемент в
начало встроенной формой нельзя, добавить … к … дописывает в конец. Поэтому
«Приписать в начало» — первая функция модуля, «без неё не выражаются ни
обращение, ни срез, ни сортировка». Реализована она свёрткой:
тотальная функция «Приписать в начало»
принимает первый: число, элементы: список числа
возвращает список числа
свёртка элементы начиная с [первый] как акк и эл → добавить эл к акк
Числа — в отсутствие убывания по мере. Шапка numbers.flang:
Здесь проходит вторая большая граница тотальности. Список убывает: у него есть хвост, и анализ это видит. Число не убывает ни во что: «n минус 1» — это результат арифметики […] Поэтому всё, что считается «пока n больше нуля», — обычная функция. Это не изъян конкретных функций ниже, а свойство языка.
Строки — в отсутствие структурного разбора. Шапка strings.flang объясняет,
что образцы пусто и голова и хвост работают только со списком, поэтому
посимвольный обход — всегда обычная функция. И тут же — приём, который спасает
часть случаев:
Обход есть у тех функций, которые удаётся выразить через «разделить … по …» и свёртку по заранее известному списку букв: такие остаются тотальными. Приём стоит запомнить — он даёт тотальные «Заменить», «Считать символ», «Позиция подстроки» и даже смену регистра.
Суммы типов вместо исключений
optional.flang и result.flang существуют по одной причине: в языке нет
исключений, а функция «найди первый подходящий элемент» обязана как-то сообщить,
что элемента нет. Возвращать ноль — значит смешивать «нашли ноль» и «не нашли».
тип «Число или ничто»
вариант «Есть число» содержит значение: число
вариант «Нет числа»
Но самое поучительное в этих файлах — не типы, а объяснение, почему у половины функций нет примеров:
Значение примера разбирается
parseLiteralValue, а тот сворачивает конструктор варианта в запись из его полей и теряет имя варианта. Такой аргумент типы отвергают, а интерпретатор не сопоставляет ни с одним образцом. Поэтому рядом с каждой такой функцией стоит её «скалярная» пара, у которой примеры есть, и она проверяет ту же логику.
То есть каждая функция, принимающая или возвращающая вариант, помечена
комментарием // Без примера: … и сопровождается обёрткой со скалярной
сигнатурой. Это ограничение реализации, а не дизайна, и оно наверняка исчезнет —
но пока формирует структуру двух модулей из пяти.
Связывание модулей
До недавнего времени импорт в flang разбирался, но не действовал. В
flang/examples/leetcode/index.json это записано как недостача:
Заголовок «модуль … использует …» разбирается, но связывания между файлами нет: каждое решение переписывает нужные ему «Приписать в начало» и «Соединить списки» заново. Стандартной библиотекой нельзя воспользоваться из решения.
На момент нашей проверки это уже неверно. Модуль flang/src/link.mjs
существует и работает. Файл flang/examples/import-check.flang — двенадцать
строк, и он этим пользуется:
модуль «Проба импорта»
использует «Списки» из "../stdlib/lists.flang"
тотальная функция «Сумма пробы»
принимает элементы: список числа
возвращает число
«Сумма» от элементы
пример «Четыре числа»
дано элементы равно [1, 2, 3, 40]
ожидается результат равен 46
$ node flang/bin/flang.mjs test flang/examples/import-check.flang --pretty
{ "valid": true, "total": 46, "passed": 46, "failed": 0, … }
Сорок шесть примеров — один свой и сорок пять пришедших из lists.flang вместе
с функциями. Это прямое следствие того, как устроено связывание.
Решения LeetCode при этом действительно дублируют вспомогательные функции —
«Приписать в начало» встречается почти в каждом файле. Их писали до появления
link.mjs, и переписывать не стали.
Импорт — это слияние, а не пространство имён
Решение объяснено в шапке link.mjs:
Импорт — это слияние объявлений, а не пространство имён. Языку без экспоненциалов и с уникальными именами вариантов пространства имён дали бы больше сложности, чем пользы: пришлось бы решать, как выглядит квалифицированное имя по-русски («Списки.Сумма»? «Сумма из Списки»?), и тащить это решение во все восемь бэкендов.
(«Восемь бэкендов» в цитате — это цели tools/ftsc, проектного компилятора
FTS-моделей; у самого flang их пять. Разбор путаницы — в
главе «flang и FTS».)
Аргумент про русский язык здесь не декоративный. Квалифицированное имя — это конструкция поверхности, а поверхность у языка русская и английская одновременно; форма, естественная в одной, будет уродливой в другой.
Цена решения названа тут же: конфликт имён становится ошибкой, а не
перекрытием. Проверяем — объявляем в своём модуле функцию «Длина», которая
уже есть в «Списках»:
{"code":"FLANG_DUPLICATE_NAME",
"message":"функция «Длина» объявлена в двух модулях:
…/flang/stdlib/lists.flang и …/probes/conflict.flang"}
Сообщение называет оба файла. Это тоже сделано специально: в link.mjs
заведены отдельные карты происхождения имён, «чтобы конфликт назвал оба файла,
а не только тот, который загрузился вторым».
Обоснование строгости: «молчаливое перекрытие функции из другого файла — источник ошибок, которые невозможно объяснить, глядя на один файл».
Практически это значит, что глобальное пространство имён у программы одно, и чем больше модулей вы подключите, тем вероятнее столкновение. Для библиотеки из пяти модулей терпимо; как это будет жить дальше — вопрос открытый, и в самом репозитории на него ответа нет.
Чего в stdlib нет
Ни ввода-вывода, ни времени, ни случайности, ни сети — и не появится в том же
виде: язык чистый, функция есть отображение аргументов в результат. Нет словаря
и множества, поэтому подсчёт вхождений везде квадратичный. Нет структур с
индексным доступом: «Элемент по номеру» написан рекурсией и стоит O(n).
Дальше — глава «Интерпретатор», про то, что всё это исполняет.