flang — полный язык поверх FTS Стандартная библиотека и связывание модулей
0%

Стандартная библиотека и связывание модулей

Стандартная библиотека и связывание модулей

Каталог 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).

Дальше — глава «Интерпретатор», про то, что всё это исполняет.

Нашли неточность? Выделите фрагмент текста — рядом появится жучок.

Нужен разбор именно вашей ситуации?

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

Доска запросов
Дальше