Стандартная библиотека и связывание модулей
Библиотека ставится вместе с компилятором: install.sh, Homebrew, deb и rpm
кладут её в share/flang/stdlib рядом с двоичным. В 0.7.29 это 64 модуля и
63 380 строк на самом flang: 2139 функций, из них 2132 тотальные, и 4143
примера. Числа сняты по установленному каталогу (ls share/flang/stdlib и
подсчёт объявлений), а не переписаны из документации. Около двух пятых строк
(26 757) — таблицы Юникода; остальное — списки, строки, числа (включая точные
целые «Bignum» и дроби «Rationals»), словари, даты, разбор форматов (JSON,
TOML, protobuf), протоколы (HTTP, TLS, DNS, WebSocket, Redis, PostgreSQL) и
криптография.
Шесть модулей, с которых стоит начать:
| Файл | Модуль | Строк | Функций (тотальных) | Примеров |
|---|---|---|---|---|
lists.flang |
«Lists» | 661 | 38 (38) | 82 |
strings.flang |
«Strings» | 684 | 39 (39) | 86 |
numbers.flang |
«Numbers» | 280 | 16 (12) | 34 |
optional.flang |
«Optional» | 208 | 11 (11) | 27 |
result.flang |
«Result» | 187 | 11 (11) | 25 |
higher-order.flang |
«Higher order» | 515 | 35 (35) | 58 |
Имя модуля — английское, имена функций внутри — русские: использует «Lists»,
потом «Сумма» от элементы.
Три границы, видные в коде
Комментариев в файлах библиотеки нет вовсе: объяснение того, что функция
обещает, записано обещаниями (обеспечивает) и именами примеров, и их
проверяет компилятор. Поэтому границы языка видны не в шапках файлов, а в
самих объявлениях.
Списки — полиморфные. lists.flang объявлен для любого типа элемента:
«Длина» от «А» принимает список «А», и рядом с примером на числах стоит
пример «Три строки — тем же телом». Одно объявление обслуживает и список числа, и список строки.
Приписать элемент в начало теперь можно встроенной формой приписать … к …, и
«Приписать в начало» — первая функция модуля после «Длины» — сводится к ней,
а её обещания называют, что именно сохраняется:
модуль «Начало списка»
тотальная функция «Приписать в начало» от «А»
принимает первый: «А», элементы: список «А»
возвращает список «А»
обеспечивает «приписывание удлиняет список ровно на один» (длина результат) равен ((длина элементы) плюс 1)
обеспечивает «приписанное встало первым» (элемент 1 в результат) равен первый
пример «В непустой»
дано первый равно 1
дано элементы равно [2, 3]
ожидается [1, 2, 3]
приписать первый к элементы
Числа — граница завершения. В numbers.flang четыре функции из
шестнадцати не тотальные: «НОД», «НОК», «Цифры» и «Сумма цифр». Они спускаются
остатком или делением, то есть шагом, который зависит от значения, а спуск по
числу компилятор доказывает только при постоянном шаге с проверкой снизу
(глава «Тотальность»). У тотальных функций
обещания судятся по одному: у «Абсолютного значения» их пять, три доказаны обо
всех входах, а два («модуль не меньше самого числа, когда число неположительно»
и «модуль не меньше противоположного») держатся только на двух примерах —
flang check --proof называет это «сетка 2 значения».
Строки — обход через список. Образцы пусто и голова и хвост работают
только со списком, поэтому строковые функции сначала превращают текст в список
(разложить … на символы, разделить … по …) и обходят уже его. Так «Strings»
остаётся тотальной целиком: 39 функций из 39.
Суммы типов вместо исключений
optional.flang и result.flang существуют по одной причине: в языке нет
исключений, а функция «найди первый подходящий элемент» обязана как-то сообщить,
что элемента нет. Возвращать ноль — значит смешивать «нашли ноль» и «не нашли».
Обе суммы параметрические:
модуль «Своё необязательное»
тип «Возможно» от «А»
вариант «Есть» содержит значение: «А»
вариант «Нет»
тотальная функция «Обернуть» от «А»
принимает значение: «А»
возвращает «Возможно» от «А»
пример «Число становится значением»
дано значение равно 7
ожидается вариант «Есть» с значение равным 7
вариант «Есть» с значение равным значение
«Результат» в result.flang устроен так же, с двумя параметрами:
«Результат» от «Значение» и «Беда», варианты «Успех» и «Ошибка». Вариант можно
и передать в пример, и ожидать из него: у «Обернуть» в библиотеке два примера, на
числе и на строке, и оба гоняются при каждой проверке.
Связывание модулей
Модуль подключается по имени из его первой строки, без пути:
модуль «Проба импорта»
использует «Lists»
тотальная функция «Сумма пробы»
принимает элементы: список числа
возвращает число
«Сумма» от элементы
пример «Четыре числа»
дано элементы равно [1, 2, 3, 40]
ожидается результат равен 46
Так написан и файл docs/examples/import-check.flang в репозитории языка.
Компилятор ищет модуль по порядку: каталог файла, каталоги выше него, каталоги
из FLANG_MODULE_DIR, а затем библиотека рядом с двоичным
(share/flang/stdlib). Тот же текст проверяется и в песочнице на /flang/: у
неё библиотека своя, в той же версии.
$ flang check импорт.flang
модуль «Проба импорта»: функций 39, из них с доказанным завершением 39; типов 1; файлов вместе с импортами 2
импорт.flang: проверено — разбор, типы, завершаемость, ядро и примеры; замечаний нет
$ flang test импорт.flang
импорт.flang: примеров 83, прошло 83, не прошло 0
Восемьдесят три примера — один свой и восемьдесят два пришедших из
lists.flang вместе с функциями. Это прямое следствие того, как устроено
связывание.
Путь в кавычках (использует «Lists» из "../stdlib/lists.flang") тоже
разбирается, но отсчитывается от файла и работает только внутри дерева
репозитория языка. Программе вне его путь не нужен.
Импорт — это слияние, а не пространство имён
Подключённый модуль не получает префикса: его функции становятся функциями программы. Квалифицированного имени («Lists.Сумма», «Сумма из Lists») в языке нет, и это решение: поверхность у языка русская и английская одновременно, и форма, естественная в одной, была бы уродливой в другой.
Цена названа тут же: конфликт имён становится ошибкой, а не перекрытием.
Объявим в своём модуле функцию «Длина», которая уже есть в «Lists»:
$ flang check конфликт.flang
FLANG_DUPLICATE_NAME, строка 4, столбец 1: в файле конфликт.flang: функция «Длина» объявлена в двух модулях: ~/.local/share/flang/stdlib/lists.flang и конфликт.flang
Сообщение называет оба файла: молчаливое перекрытие функции из другого файла — источник ошибок, которые невозможно объяснить, глядя на один файл.
Практически это значит, что глобальное пространство имён у программы одно, и чем больше модулей вы подключите, тем вероятнее столкновение.
Выборочный импорт: только
Отсюда и вторая форма заголовка — перечислить, что именно берём:
модуль «Выборочный импорт»
использует «Lists» только «Длина», «Обратить», «Приписать в начало»
тотальная функция «Длина задом наперёд»
принимает элементы: список числа
возвращает число
пример «Три числа»
дано элементы равно [1, 2, 3]
ожидается 3
«Длина» от («Обратить» от элементы)
Разница видна по счётчику примеров. Полный импорт «Lists» приводил с собой восемьдесят два чужих примера; выборочный приводит только те, что объявлены у взятых функций:
$ flang test выборочно.flang
выборочно.flang: примеров 10, прошло 10, не прошло 0
Не взятое остаётся невидимым — и невидимым честно, с обычной диагностикой имени:
$ flang check невидимо.flang # использует «Lists» только «Длина», зовёт «Сумма»
FLANG_UNKNOWN_NAME, строка 4, столбец 1: в файле невидимо.flang: имя «Сумма» не входит в список «только» у ввоза «Lists» из ~/.local/share/flang/stdlib/lists.flang: впишите его в этот список или не зовите — проверка не зависит от того, ввёз ли тот же модуль шире другой файл
FLANG_UNKNOWN_NAME, строка 7, столбец 3: неизвестная функция «Сумма»
…
С flang 0.7.32 выбранные функции получают свои внутренние зависимости. В примере для «Среднего» достаточно ввезти «Длину» и «Сумму». В 0.7.29 приходилось отдельно перечислять вспомогательные функции «Суммы» (digitable-lol/flang#447).
модуль «Среднее»
использует «Lists» только «Длина», «Сумма»
тотальная функция «Среднее»
принимает элементы: список числа
возвращает число
пример «Три числа»
дано элементы равно [1, 2, 3]
ожидается 2
(«Сумма» от элементы) делить на («Длина» от элементы)
Форма только не украшение. В каталоге flang/self/, где компилятор языка
написан на самом языке
(глава «Ядро FTS на flang»), почти каждый
заголовок пользуется ею. Так начинается лексер компилятора
(flang/self/lexer.flang):
модуль «Лексер flang»
использует «Strings» только «Соединить строки», «Шаг склейки строк», «Заменить», «Позиция подстроки», «Приписать строку в начало»
В списке стоит и «Шаг склейки строк» — вспомогательная функция, на которой
построена «Соединить строки»: та же граница только, что и выше. Модули
компилятора между собой ввозятся так же, по спискам: файлы большие, имена в
них общие, и слияние целиком столкнуло бы половину.
Тот же приём решает и задачу переиспользования: вывод разобранной программы в
JSON в self/ не написан заново, а ввозится из одного модуля.
Библиотека на функциях-значениях лежит в stdlib
Функция в flang — значение, и у библиотеки есть модуль, который на этом
построен: higher-order.flang, «Higher order». Свёртка, отображение, фильтр,
поиск, сортировка и композиция принимают функцию аргументом:
модуль «Своё отображение»
тотальная функция «Удвоить»
принимает х: число
возвращает число
х плюс х
тотальная функция «Отобразить числа»
принимает ф: функция из числа в число, элементы: список числа
возвращает список числа
обеспечивает «отображение сохраняет длину» (длина результат) равен (длина элементы)
пример «Удвоить каждое»
дано ф равно функция «Удвоить»
дано элементы равно [1, 2, 3]
ожидается [2, 4, 6]
отобразить элементы как эл → ф от эл
В самой библиотеке «Отобразить» объявлена шире — от «А» и «Б», с аргументом
ф: функция из «А» в «Б». В модуле 35 функций, все тотальные, и 58 примеров;
два модуля он ввозит выборочно (использует «Numbers» только …,
использует «Lists» только «Приписать в начало»).
Функции-значения ничего не стоят в сгенерированном коде: перед генерацией компилятор одним проходом превращает их в обычные вызовы, поэтому граф вызовов остаётся конечным, а завершение — доказуемым. Цена другая: этому проходу нужна вся программа целиком.
Чего в stdlib нет
Ввода-вывода в виде функций библиотека не даёт, и это не пробел: файл, сеть,
процесс и время программа описывает значением — запросом к среде, а исполняет
среда исполнения (flang io, глава
«Синтаксис по факту», раздел про план). Набор
таких запросов задаёт сам язык. Протокольные модули библиотеки («Requests and
responses», «TLS records», «DNS», «Redis», «Postgres») — чистые функции:
они собирают и разбирают сообщения, а отправляет байты среда.
Словарь и множество есть, но это обычные модули на самом flang, без отдельной поддержки компилятора: «Dictionary», «Hash map», «Tree map» и «String sets». Структур с индексным доступом за O(1) нет: «Элемент» в «Lists» идёт по списку и стоит O(n).
Дальше — глава «Интерпретатор», про то, что всё это исполняет.