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

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

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

Библиотека ставится вместе с компилятором: 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).

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

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

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

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

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