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

Система типов и то, чего в ней нет

Система типов и то, чего в ней нет

Проверка типов живёт в flang/src/types.mjs — 1115 строк. Вход — AST из раздела 5 спецификации, выход — диагностики в формате ядра FTS и таблица сигнатур. Читать этот файл приятно: почти каждое решение объяснено вместе с отвергнутой альтернативой.

Почему здесь нет Хиндли — Милнера

Вопрос напрашивается: язык функциональный по духу, почему не классический вывод типов с унификацией? Ответ записан в шапке модуля:

Функции в flang не являются значениями первого класса (SPEC, раздел 3), поэтому единственные места, где тип действительно неизвестен, — это локальное пусть, элемент списка, накопитель свёртки и пустой список. Всё остальное задано объявленной сигнатурой. Унификация с переменными типов дала бы ту же силу вывода, но сообщения об ошибках стали бы говорить о «t7 против t12» вместо «ветви „если“ разных типов» — для языка, где типы объявляются, это чистый проигрыш.

Вместо унификации — двунаправленная проверка: ожидаемый тип течёт сверху вниз и решает единственный по-настоящему неоднозначный случай — пустой список. пустой список в контексте список строки получает тип список строки, а без контекста — «список неизвестного», совместимый с чем угодно и потому не дающий каскада ложных ошибок.

Проверить легко:

тотальная функция «Пустой список без контекста»
  принимает н: число
  возвращает список числа
  пример «Пусто»
    дано н равно 1
    ожидается пустой список
  пустой список

Проходит: объявленный возвращаемый тип и есть тот контекст, которого не хватало.

Сообщения действительно получились человеческие. Ветви разного типа:

$ … если н больше 0 то 1 иначе "строка"
{"code":"FLANG_TYPE","message":"ветви «если» разных типов: число и строка"}

Какие типы есть

Тип := строка | число | признак | ничто
     | список Тип
     | «Имя объекта»          запись
     | «Имя типа»             сумма

Это раздел 3 спецификации, за вычетом строки Тип → Тип, о которой ниже.

Скалярные псевдонимы нормализуются в одном месте — таблица SCALAR_ALIASES в types.mjs. Это нужно, потому что имена приходят из трёх источников: русской поверхности (число, строка, признак, ничто), английской (number, string, boolean, null) и моста из FTS (Число, Деньги, Строка, Дата, Признак). Деньги — это число, Дата — строка.

Есть и четвёртый случай, который стоит отметить отдельно, потому что он объясняет, зачем в системе типов вообще нужен джокер. FTS умеет описывать поля именами состояний — «Скоринг пройден». Это маркеры доказательств, а не значения; ядро FTS их не проверяет. Мост переводит их в unknown с сохранённым именем, и тайпчекер обязан их пропускать — иначе он отверг бы существующие модели, что запрещено обещанием совместимости.

Исчерпывающность разбора

Проверяется, и это одна из самых полезных вещей в языке:

тип «Цвет»
  вариант «Красный»
  вариант «Зелёный»
  вариант «Синий»

тотальная функция «Код цвета»
  принимает цвет: «Цвет»
  возвращает число
  разбор цвет
    случай «Красный»
      то 1
    случай «Зелёный»
      то 2
{"code":"FLANG_MATCH_NOT_EXHAUSTIVE","message":"разбор «Цвет» не покрывает «Синий»",
 "span":{"line":11,"column":5}}

Сообщение называет конкретный непокрытый вариант, а не факт неполноты. Есть и парный код FLANG_MATCH_UNREACHABLE — на недостижимый случай.

Главное решение: функции не значения

В таблице типов спецификации есть строка Тип → Тип, и рядом с ней — уточнение: «функция (только как объявление, не значение)». Дальше идёт формулировка, которую стоит привести целиком:

Функции не являются значениями первого класса. Это осознанное ограничение, а не недоделка: без экспоненциалов остаётся выразимой генерация в языки без замыканий, а анализ завершаемости остаётся простым и предсказуемым.

Два следствия названы прямо, и оба подтверждаются кодом.

Кодогенерация. Печать в C — это flang/src/emit/c.mjs, C99 без замыканий. Если бы функция была значением, пришлось бы печатать окружения, боксинг и управление их временем жизни. Без экспоненциалов вызов печатается вызовом.

Анализ завершаемости. flang/src/totality.mjs отслеживает происхождение каждого значения — «частью какого параметра оно является и насколько глубоко разобранной частью». Функция-значение сделала бы это отслеживание межпроцедурным и, скорее всего, неразрешимым на практике.

Взамен всё, что обычно требует функций высшего порядка, покрыто встроенными формами отобразить / отфильтровать / свёртка, принимающими тело, а не функцию:

отфильтровать элементы где эл → эл не равен значение

Работает, читается неплохо. Но: собственную функцию высшего порядка вы не напишете, а значит и не абстрагируете повторяющийся обход.

Чего в системе типов нет

Параметрического полиморфизма

Это самое дорогое из отсутствующего. Признание — прямо в шапке flang/stdlib/lists.flang:

Почему модуль про числа, а не «про любой список»: типы в flang не параметрические (SPEC, раздел 3), а функции не являются значениями. Поэтому «Сортировать» для строк — это отдельная функция с отдельным телом, а не та же самая с другим параметром типа.

Практически это значит, что «Обратить» для список числа и для список строки — две функции с одинаковым телом. И хуже: опциональное значение приходится заводить отдельно под каждый тип, потому что имена вариантов обязаны быть уникальны в модуле. flang/stdlib/optional.flang устроен как «Число или ничто» именно поэтому, и в комментарии написано: «Для строк пришлось бы завести второй тип с другими именами вариантов».

Псевдонимов типов

Спецификация показывает тип «Строка счёта» это список «Позиция», и лексер знает ключевое слово alias: ["это"]. Но в списке недостающего (flang/examples/leetcode/index.json) записано, что конструкция разбирается в узел alias, а types.mjs раскладывает объявления только на суммы и записи — и псевдоним становится записью без полей. Пользоваться псевдонимами нельзя. В корпусе .flang-файлов их и нет ни одного.

Порядка на строках

больше и меньше для строк отвергаются на check:

{"code":"FLANG_TYPE","message":"левый операнд сравнения «gt» имеет тип строка:
 сравнения порядка допустимы только для чисел"}

Это делает невыразимой сортировку строк, и в списке невыразимых задач LeetCode так и записано (задача 179, Largest Number).

Отметим для точности: в index.json этот пункт описан иначе — как расхождение слоёв, где «types.mjs разрешает „больше“/„меньше“ для строк, а interpret.mjs на них бросает FLANG_TYPE», то есть программа проходит проверку и падает при запуске. На момент нашей проверки расхождения нет: отказ приходит на check, до запуска. Похоже, долг закрыли; сам пункт в index.json обновить не успели.

Обработки ошибок в типах

Тип не умеет сказать «эта функция может не справиться». Исключений в языке нет, отказ встроенной формы (к числу от "abc", голова от пустого списка) прекращает вычисление целиком и не перехватывается. Единственный способ — вернуть сумму типов вручную; так устроен flang/stdlib/result.flang:

тип «Результат числа»
  вариант «Успех» содержит значение: число
  вариант «Ошибка» содержит сообщение: строка

С важной оговоркой из шапки того же файла: «Проверка должна идти ДО опасной встроенной формы, а не после: обработать её отказ уже нельзя».

Как к этому относиться

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

Настоящая цена — не в отсутствии изысков, а в дублировании: без параметрического полиморфизма стандартная библиотека вынуждена повторять одни и те же алгоритмы под каждый тип элемента. Насколько это терпимо на объёме больше стандартной библиотеки, пока неизвестно — такого объёма на flang ещё не написано, кроме core/ (глава «Ядро FTS на flang»).

Дальше — глава «Тотальность», про самую нестандартную часть проверок.

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

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

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

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