flang — язык с доказуемым завершением Синтаксис по факту: модули, функции, выражения, образцы
0%

Синтаксис по факту: модули, функции, выражения, образцы

Синтаксис по факту: модули, функции, выражения, образцы

Правило этой статьи: показывается только то, что либо взято дословно из файла репозитория, либо написано нами и проверено запуском. Там, где мы пробовали угадать форму и ошиблись, приведён отказ парсера — он информативнее описания.

Файл целиком

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

модуль «Синтаксис»

объект «Позиция»
  цена является числом
  количество является числом

тотальная функция «Стоимость позиции»
  принимает цена: число, количество: число
  возвращает число
  пример «Три по десять»
    дано цена равно 10
    дано количество равно 3
    ожидается 30
  пусть поз равно запись «Позиция» с цена равным цена и количество равным количество
  поз.цена умножить на поз.количество

тотальная функция «Деление и остаток»
  принимает а: число, б: число
  возвращает число
  пример «Семь и два»
    дано а равно 7
    дано б равно 2
    ожидается 4.5делить на б) плюсостаток от б)

тотальная функция «Скидка»
  принимает сумма: число
  возвращает число
  пример «Десять процентов от тысячи»
    дано сумма равно 1000
    ожидается 100
  10 процентов от сумма
$ node flang/bin/flang.mjs test синтаксис.flang --pretty
{ "valid": true, "total": 3, "passed": 3, "failed": 0, … }

Заголовок модуля

Файл начинается строкой модуль «Имя». Импорты идут с отступом под ней:

модуль «Проба импорта»
  использует «Списки» из "../stdlib/lists.flang"

Это файл flang/examples/import-check.flang целиком (без функции). Путь — в обычных двойных кавычках и разрешается относительно текущего файла.

Есть и вторая форма — взять из модуля не всё, а перечисленное:

модуль «Печать в C»
  использует «Печать JSON» из "../core/json.flang" только «Значение», «Скаляр»,
    «Экранировать», «Печать строки», «Печать массива»

Это начало flang/self/emit-c.flang (список сокращён). Имена перечисляются через запятую, и всё, что в список не попало, из модуля не приходит вовсе — обращение к нему даёт обычный FLANG_UNKNOWN_NAME.

Ключевое слово экспортирует в таблице лексера есть, но ни в одном .flang-файле репозитория не используется; без него виден весь модуль. Подробнее про связывание — глава «Стандартная библиотека».

Комментарии — // до конца строки. Файлы stdlib и core открываются многострочными блоками таких комментариев, в которых объясняется, почему модуль устроен именно так; читать их стоит.

Объявление типов

Записи объявляются словом объект — прямо как в FTS:

объект «Группа»
  ключ является строкой
  слова является список строки

Это из flang/examples/leetcode/049-group-anagrams.flang. Суммы типов — словом тип, с перечислением вариантов:

тип «Число или ничто»
  вариант «Есть число» содержит значение: число
  вариант «Нет числа»

Это flang/stdlib/optional.flang. Вариант либо несёт поля (содержит имя: тип, через запятую), либо не несёт ничего. Имена вариантов обязаны быть уникальны в пределах модуля.

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

Функции

тотальная функция «Первый элемент или запасное»
  принимает элементы: список числа, запасное: число
  возвращает число
  пример «Список не пуст»
    дано элементы равно [4, 5]
    дано запасное равно 0
    ожидается 4
  пример «Пустой список даёт запасное»
    дано элементы равно пустой список
    дано запасное равно 42
    ожидается 42
  «Значение или запасное» от («Первый элемент» от элементы) и запасное

Разбираем по строкам:

  • тотальная — необязательная пометка класса (глава «Два класса программ»);
  • принимает — параметры через запятую, каждый в форме имя: тип;
  • возвращает — один тип;
  • пример — блок с дано на каждый параметр и одним ожидается;
  • последнее выражение блока — тело функции.

Порядок свободный: примеры в корпусе стоят и до тела, и после него. Тело — это одно выражение, а не последовательность операторов; у языка нет return, потому что возвращать больше нечего.

Форма ожидается 4 — короткая. В унаследованных от FTS утилитах встречается полная — ожидается результат равен 2000.

Типы в сигнатурах

Скаляры: число, строка, признак, ничто. Формы склонений тоже читаются — лексер знает числа, числом, числу, строки, строкой, строку, признака, признаком. Отсюда естественное список числа вместо список число.

Списки: список числа, список строки, список «Группа», и даже вложенно — список список строки (из решения про анаграммы).

Именованные типы пишутся в ёлочках: возвращает «Число или ничто».

Унаследованные от FTS имена деньги и дата лексер знает; тайпчекер приравнивает Деньги к числу, а Дата — к строке.

Выражения

Связывание

пусть удвоенное равно «Умножить значение» от («Первый элемент» от элементы) и 2
«Значение или запасное» от удвоенное и запасное

пусть не требует слова в: тело идёт следующей строкой того же уровня.

Ветвление

Блочная форма:

если акк начинается с "!"
  то акк
  иначе
    если "([{" содержит буква
      то соединить акк с буква
      иначе …

И однострочная — из того же файла (flang/examples/leetcode/020-valid-parentheses.flang):

если закрывающая равен "]" то "[" иначе "{"

Отдельной конструкции «иначе если» нет — вложенные если в ветке иначе образуют лестницу. Логических связок тоже нет: ни и, ни или, ни не для признаков. Конъюнкция пишется вложенными если, отрицание — сравнением равен нет. Это заметная шероховатость, и она честно записана в flang/examples/leetcode/index.json в списке недостающего.

Вызов

«Длина» от хвоста
«Значение или запасное» от удвоенное и запасное
«Символы с позиции» от текст и (позиция плюс 1)

Имя функции — всегда в кавычках, аргументы соединяются словом и. Вызвать функцию без аргументов нечем: применение записывается только как «Имя» от ….

Арифметика и сравнения

Инфиксные: плюс, минус, умножить на, делить на, остаток от, процентов от. Последние два стоит запомнить именно как инфиксные — мы попробовали написать остаток от а и б по аналогии с вызовом и получили:

{"code":"FLANG_PARSE","message":"не разобрана конструкция: неожиданное 'остаток от'",
 "span":{"line":24,"column":25}}

Правильно — а остаток от б.

Сравнения унаследованы от FTS: равен, не равен, больше, меньше, не больше, не меньше. Формы равна, равно, равным — то же самое.

Порядок сравнения (больше/меньше) работает только для чисел. На строках получите отказ ещё на check:

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

Записи и варианты

Конструирование записи и вариантa устроено одинаково — словом с и перечислением полей через и:

запись «Группа» с ключ равным ключ и слова равным (добавить слово к группа.слова)
вариант «Есть число» с значение равным голова

Вариант без полей пишется просто: вариант «Нет числа».

Доступ к полю — через точку: группа.ключ, диагностика.код. Единственная точка в языке, где точка вообще используется.

Записи нельзя записать литералом. Мы попробовали очевидное:

$ … дано поз равно {цена: 10, количество: 3}
{"code":"FLANG_PARSE","message":"не разобрана конструкция: неожиданное '{'"}

В корпусе записи всегда строятся конструктором запись «…» с …, а в примерах не встречаются вовсе.

Сопоставление с образцом

разбор элементов
  случай пусто
    то 0
  случай голова и хвост
    то 1 плюс «Длина» от хвоста

Образцы, которые встречаются в репозитории:

Образец Что связывает
случай пусто пустой список
случай голова и хвост голову и хвост непустого списка
случай вариант «Есть число» с значение как зн вариант и его поле под именем зн
случай «Нет числа» вариант без полей

Обратите внимание на голова и хвост: имена не выбираются, это фиксированная форма из таблицы ключевых слов (headTail: ["голова и хвост", "head and tail"]).

Форма случай вариант «Имя» с поле как имя — явная, с ключевым словом вариант. Именно ей пользуется стандартная библиотека, и не из вкуса. В flang/SPEC.md, раздел 10, описано известное ограничение: лексер решает, что перед ним, до того как становится известен тип, поэтому вариант, названный ключевым словом, в образце не разбирается.

Имя варианта Во что превращается Что видит пользователь
Да, Нет литерал признака FLANG_TYPE: образец-литерал вместо варианта
Плюс, Минус арифметический оператор FLANG_PARSE: ожидался образец
Больше, Меньше сравнение FLANG_PARSE: ожидался образец

Мы проверили первую строку — тип с вариантами Да и Нет разбирается, а образец нет:

{"code":"FLANG_TYPE","message":"образец-литерал имеет тип признак,
 а разбирается «Ответ»","severity":"error","span":null}

Спецификация тут же признаёт долг: диагностика не называет настоящую причину, и «сообщение обязано говорить „имя варианта совпадает с ключевым словом“, а не „ожидался образец“». С явной формой случай вариант «Да» тот же файл проходит check без единой диагностики.

Встроенные формы над коллекциями

map/filter/fold принимают не функцию, а тело. Заведено это было тогда, когда функция значением ещё не была; теперь она значение (раздел ниже), но формы остались — и остались основным способом обхода, потому что тело не требует объявлять функцию ради одного выражения:

свёртка элементы начиная с [первый] как акк и эл  добавить эл к акк
отобразить группы как группа  группа.слова
отфильтровать элементы где эл  эл не равен значение

Стрелка отделяет тело от заголовка в однострочной записи; в многострочной тело просто уходит на следующий уровень отступа:

свёртка группы начиная с нет как акк и группа
  если акк равен да то да иначе группа.ключ равен ключ

Также есть добавить … к … (дописывает в конец) и длина. Приписать в начало встроенной формы нет — flang/stdlib/lists.flang заводит для этого первую функцию модуля.

Строки

длина текст
символ 2 в текст
подстрока акк с 1 по ((длина акк) минус 1)
соединить акк с буква
разделить текст по буква
"([{" содержит буква
акк начинается с "!"
к строке (…)
к числу (…)

Две семантические детали зафиксированы в спецификации и повторяются всеми слоями: индексация с 1, включительно с обоих концов, и длина в кодовых точках, а не в единицах UTF-16. Мы проверили второе:

тотальная функция «Длина текста»
  принимает текст: строка
  возвращает число
  пример «Эмодзи считается одним»
    дано текст равно "мир 🌍"
    ожидается 5
  длина текст

Пример проходит. Обоснование в спецификации прямое: «иначе кириллица и эмодзи считаются неверно».

Ещё одна деталь для тех, кто сверяется с index.json: там записано, что форма символ не проходит check из-за расхождения порядка аргументов между парсером и тайпчекером. На момент нашей проверки это уже неверно — символ 2 в текст проходит check и возвращает "и" от "мир".

Литералы

42, -3, 0.5, "строка", да, нет, ничто, [1, 2, 3], пустой список.

Экспоненциальная запись (1e3) лексером flang читается — регулярное выражение в lexer.mjs её допускает. А вот лексер FTS, переписанный на flang в flang/core/lexer.flang, её не поддерживает: это записано отдельным долгом в flang/core/SPEC.md. Два разных лексера, не перепутайте.

Категорная поверхность: стрелка, композиция, цепочка, единица

Это самое новое в синтаксисе, и появилось оно уже после того, как остальная глава была написана. Замысел объяснён в flang/cat/SPEC.md одной фразой: семантика языка давно категорная, а поверхность об этом молчала. Тип — это объект, функция — морфизм, вложенный вызов — композиция, запись — произведение, сумма типов — копроизведение, ничто — терминальный объект. Названий у всего этого в языке не было.

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

модуль «Продажи»

объект «Заказ»
  сумма является числом

объект «Отгрузка»
  номер является строкой

объект «Счёт»
  итог является числом

морфизм «отгрузить» из «Заказ» в «Отгрузка»
морфизм «выставить» из «Отгрузка» в «Счёт»

морфизм «оформить» это «выставить» после «отгрузить»

единица «Заказ»

цепочка «провести»
  сначала «отгрузить»
  затем «выставить»

flang check на этом файле отвечает valid: true и ни одной диагностики.

Разберём по словам.

морфизм «имя» из «А» в «Б» — стрелка с доменом и кодоменом. после — композиция в математическом порядке: правая применяется первой. цепочка — та же композиция в порядке чтения, для длинных конвейеров: без неё «в» после («б» после «а») читается наизнанку. единица «А» — тождественная стрелка объекта; в AST она приезжает морфизмом с identity: true, доменом и кодоменом «А», и композируется с любой стрелкой из своего объекта.

Стыковка проверяется компилятором — и это доказательство

Вот ради чего стоит читать этот раздел, даже если теоркат вам безразличен. В flang/cat/SPEC.md есть отдельный раздел «Граница честности: доказано против проверено», и композиция стоит на доказанной стороне вместе с завершением и типами:

«Б» после «А» не соберётся, если кодомен «А» не равен домену «Б»; цепочка не соберётся, если рвётся в середине.

Это утверждение обо всех входах, без решателя и без примеров. Проверяется оно ровно так, как обещано, — переставим множители местами:

$ node flang/bin/flang.mjs check продажи.flang --pretty
{
  "valid": false,
  "diagnostics": [ {
    "code": "FLANG_COMPOSE_MISMATCH",
    "message": "композиция «оформить» не стыкуется: «выставить» приводит в «Счёт»,
                а «отгрузить» ожидает «Заказ»",
    "severity": "error",
    "span": { "line": 15, "column": 1 }
  } ]
}

Своя диагностика, а не общий FLANG_TYPE, — решение записанное: «по коду машина понимает, что чинить». Разрыв в середине цепочки называется так же точно: «композиция «провести» не стыкуется: «отгрузить» приводит в «Отгрузка», а «оплатить» ожидает «Счёт»». Порядок объявлений при этом не решает: композиция вправе сослаться на морфизм, объявленный ниже, — но хождение по кругу даёт диагностику, а не зависание.

Промежуточные звенья цепочки служебные: у цепочки из трёх шагов в AST две композиции, и одна помечена internal: true — имени она не занимает.

Функтор, бифунктор, изоморфизм, моноид — то, чего в прошлой редакции не было

В прошлой редакции здесь стоял абзац о том, что из восьми шагов контракта flang/cat/SPEC.md сделан первый, и приводились две команды, где монада и моноид не разбираются. С тех пор прошли сутки, и половина этого абзаца стала неправдой. Разбирается теперь и моноид, и функтор, и бифунктор, и изоморфизм. Не разбирается по-прежнему монада — и почему именно она, разобрано ниже отдельно, потому что причина у неё содержательная.

Одиннадцать новых слов легли в таблицу лексера за эти сутки: изоморфизм, прямой морфизм, обратный морфизм, моноид, носитель, операция, обратный элемент, бифунктор, объекты, морфизмы — и, из совсем другой области, план.

Функтор. Отображение категории в категорию: объектам объекты, стрелкам стрелки.

функтор «Заказ в счёт» из «Продажи» в «Биллинг»
  объект «Заказ» отображается в «Счёт»
  объект «Отгрузка» отображается в «Акт»
  морфизм «отгрузить» отображается в морфизм «выставить»

Строк сохраняет композицию и сохраняет единицы в поверхности нет, и это решение, а не упущение. Записано оно так: «отображение, не сохраняющее композицию, функтором не является, и разрешение на проверку закона продавало бы имя вместо содержания». Законы не объявляются — они доказываются, сличением объявленных имён. Убрать одну строку — и компилятор называет, чего не хватает, своим кодом:

$ node flang/bin/flang.mjs check функтор.flang
{"code":"FLANG_FUNCTOR_OBJECT_MISSING",
 "message":"функтор «Заказ в счёт» отображает морфизм «отгрузить», но не отображает
            его кодомен «Отгрузка»: без образа объекта образ стрелки не с чем согласовать"}

Своих кодов у новых конструкций набралось двенадцать — FLANG_FUNCTOR_OBJECT_TWICE, FLANG_FUNCTOR_OBJECT_MISSING, FLANG_FUNCTOR_ARROW_MISMATCH, FLANG_FUNCTOR_COMPOSITION, FLANG_FUNCTOR_IDENTITY и их бифункторные двойники, плюс FLANG_ISO_MISMATCH, FLANG_ISO_IDENTITY_MISSING. Требование то же, что у композиции: по коду машина понимает, что чинить.

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

бифунктор «Пара» из «Продажи» и «Продажи» в «Пары»
  объекты «Заказ» и «Заказ» отображаются в «Пара заказов»
  морфизмы «отгрузить» и «отгрузить» отображаются в морфизм «отгрузить обе»

Общего вида — из «Значения» и «Значения» в «Значения» с образом «Пара А Б» — здесь нет: «А» и «Б» были бы переменными типа, а параметрические типы в языке появились только в объявлениях типов и функций, до теорката они ещё не дошли.

Граница «доказано / проверено» проходит внутри одной конструкции

Это самое интересное, что случилось с языком за сутки, и учебники так обычно не пишут. Возьмём моноид:

моноид «Сложение чисел»
  носитель число
  операция «Сложить»
  единица 0
  обратный элемент «Противоположное»

Слова группа в языке нет вовсе — оно занято как имя. Проверяется это grep-ом и воспроизводится: «групп…» в ёлочках встречается в репозитории 57 раз (из них 40 — ровно «группа»), и ключевое слово запретило бы каждое из них. Поэтому обращение объявляется строкой обратный элемент внутри моноида. В контракте это названо не потерей, а находкой: группа и есть моноид с обращением, и второе слово развело бы две проверки, обязанные совпадать во всём, кроме одного закона.

Теперь главное. В одной этой конструкции граница честности проходит посередине.

Доказывается устройство. Операция обязана быть функцией двух аргументов носителя, возвращающей носитель; единица — значением носителя; обращение — функцией носителя в носитель. Это утверждения обо всех входах, и берутся они из объявлений.

Проверяются на конечной сетке сами законы — ассоциативность, нейтральность, обратимость. Доказать их нельзя: это равенства вычислений на всех значениях носителя. Можно только предъявить контрпример — что компилятор и делает. Подменим в том же файле сложение вычитанием, у которого не выполняется ни один из трёх законов:

моноид «Вычитание чисел»
  носитель число
  операция «Вычесть»
  единица 0
  обратный элемент «Противоположное»
$ node flang/bin/flang.mjs check вычитание.flang
FLANG_MONOID_IDENTITY — моноид «Вычитание чисел»: единица не нейтральна слева:
                        операция от единицы и 7 дала -7
FLANG_MONOID_ASSOC    — моноид «Вычитание чисел»: операция не ассоциативна:
                        на 0, 0, 7 слева вышло -7, справа 7
FLANG_GROUP_INVERSE   — моноид «Вычитание чисел»: обращение не даёт единицу:
                        на 7 вышло -14 слева и 14 справа

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

А у изоморфизма равенство не проверяется вовсе.

изоморфизм «Заказ и накладная» из «Заказ» в «Накладная»
  прямой морфизм «выписать»
  обратный морфизм «по накладной»

flang check на этом файле отвечает valid: true — и это честный ответ на единственный вопрос, на который он может ответить. Доказывается сличением объявлений: объекты объявлены; обе стрелки объявлены; концы сходятся крест-накрест; единицы обоих объектов объявлены, потому что закон говорит именно о них. Перепутаем направление — компилятор ловит:

{"code":"FLANG_ISO_MISMATCH",
 "message":"изоморфизм «Заказ и накладная»: обратный морфизм «выписать» обязан
            вести из «Накладная» в «Заказ», а ведёт из «Заказ» в «Накладная»"}

Уберём единица «Накладная» — ловит и это, причём формулировкой, которую стоит прочитать целиком:

{"code":"FLANG_ISO_IDENTITY_MISSING",
 "message":"изоморфизм «Заказ и накладная» обещает, что кругооборот через «Накладная»
            даёт «единица Накладная», а единица объекта «Накладная» не объявлена:
            закон говорит о том, чего в программе нет"}

А вот само равенство «обратный после прямого = единица» не проверяется ни доказательством, ни сеткой. Причина не в недосмотре: у стрелки категории нет тела. морфизм «ф» из «А» в «Б» — объявление без вычисления, вычислять на сетке нечего. Значит объявление изоморфизма — это допущение автора, и контракт называет его так прямым текстом.

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

Конструкция Устройство Закон
композиция, цепочка доказано доказано (стыковка доменов)
функтор, бифунктор доказано доказано (сличение объявлений)
моноид, группа доказано проверено на конечной сетке
изоморфизм доказано не проверяется вовсе
монада конструкции нет

Три разных ответа на вопрос «а это точно так?» внутри одной поверхности одного языка. Тот, кто читает только слово «доказуемый» на титульной странице, все три прочитает одинаково.

Почему монады до сих пор нет — и это не про лень

$ printf 'модуль «П»\n\nмонада «М»\n  эндофунктор «М»\n' > м.flang
$ node flang/bin/flang.mjs check м.flang
{"error":"не разобрана конструкция: неожиданное 'монада'", …}

Причина названа в flang/cat/SPEC.md и в flang/PLAN.md одинаково: монада — эндофунктор плюс два естественных преобразования, а соединение требует типа M(M(A)). Имена вариантов в flang уникальны на весь модуль — это решение записано прямо в types.mjs, потому что конструктор вариант «Есть» с … не называет сумму и по имени варианта сумма обязана находиться однозначно. Следствие: без параметрических типов M(M)отдельный тип с другими именами конструкторов, а законы монады трогают . Три руками написанные копии, у которых общего только замысел автора.

Параметрические типы за эти сутки появились (раздел ниже), и первое, что они сняли, — именно это:

тотальная функция «Двойное»
  принимает значение: число
  возвращает «Возможно» от («Возможно» от числа)
  пример «Вложение того же типа»
    дано значение равно 5
    ожидается вариант «Есть» с значение равным (вариант «Есть» с значение равным 5)
  вариант «Есть» с значение равным («Обернуть число» от значение)

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

Со словом функтор остаётся тонкость, на которой легко ошибиться, и она пережила появление настоящего функтора. Файл-функтор FTS — конструкция наследия — тоже разбирается и тоже даёт valid: true, но уезжает в узел ftsLegacy / functorFile и никаких законов за собой не тянет. Отличить можно командой ast: у настоящего функтора есть отображения объектов и стрелок, у файла-функтора в legacy лежит запись с from и to.

И одно уточнение, без которого весь раздел читается неверно. Морфизм не заменяет функцию и не отменяет её. Это отдельное объявление; решение записано прямо: «объявить функцию частным случаем морфизма красивее, но это переписывает 1711 объявлений в репозитории и ломает всё, что уже доказано побайтовой сверкой». Тотальная функция — это доказанное завершение вычисления; морфизм — стрелка между объектами. Разные вещи, и обе остаются. Больше того, у морфизма есть и третья ипостась: старая форма FTS, где морфизм это утверждение «если А, то Б», продолжает работать и в стрелки не попадает вовсе — 24 морфизма в моделях репозитория написаны именно так.

Функция как значение — и ни одного нового слова

В прошлой редакции этой главы стояло: «функций как значений нет». Теперь есть, и поверхность не стоила языку ни единого нового слова.

тотальная функция «Удвоить»
  принимает х: число
  возвращает число
  х плюс х

тотальная функция «Применить дважды»
  принимает ф: функция из числа в число, х: число
  возвращает число
  ф отот х)

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

flang check отвечает valid: true, и — это главное — все три функции помечены total: true. flang test даёт 1 из 1.

Три формы, и все на занятых словах:

форма что означает слова
функция из числа и строки в признак тип функции функция, из, и, в
функция «Удвоить» функция как значение функция
ф от 5 применение значения-функции от

Почему ни одного нового слова — объяснено в flang/cat/HOF.md цифрами, а не принципом, и проверка приведена дословно. Мы её повторили:

$ grep -rowE --include=*.flang --include=*.fts "«функция»" . | wc -l
173
$ grep -rowE --include=*.flang --include=*.fts "«из»|«в»|«и»|«от»" . | wc -l
46

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

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

Чем за это заплачено

функция «Обернуть» при полиморфной «Обернуть» даёт FLANG_TYPE_PARAM, а не значение: у полиморфной функции типов столько, сколько подстановок, а у значения он один. Каррирования и частичного применения нет — арность в flang свойство функции, а не сахар над цепочкой одноместных, и это решение записано как «чего решено не делать никогда».

И самое дорогое: писать такое в программах репозитория пока нельзя. Не потому, что не работает, а потому, что self/parser.flang этой формы не разбирает, а корпус сверки самоприменения собирается по маске каталога — первый же файл stdlib с функция из числа в число роняет неподвижную точку. Измерено, а не предположено: модуль с функциями-значениями, положенный в flang/stdlib/, роняет пять тестов, и flang₁ отвечает дословно — «не разобрана конструкция: ожидался тип».

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

Тоже без единого нового слова: от и и были в таблице с первого дня.

тип «Возможно» от «А»
  вариант «Есть» содержит значение: «А»
  вариант «Ничего»

тотальная функция «Обернуть число»
  принимает значение: число
  возвращает «Возможно» от числа
  пример «Число заворачивается»
    дано значение равно 5
    ожидается вариант «Есть» с значение равным 5
  вариант «Есть» с значение равным значение

тотальная функция «Обернуть строку»
  принимает значение: строка
  возвращает «Возможно» от строки
  пример «Строка заворачивается»
    дано значение равно "мир"
    ожидается вариант «Есть» с значение равным "мир"
  вариант «Есть» с значение равным значение

Одно объявление обслуживает обе подстановки; flang test даёт 3 из 3 вместе с вложенным случаем из раздела про монаду. от после имени объявления вводит параметры, от после имени типа в позиции типа подставляет аргументы — читается одинаково, «Возможно от А».

Одна настоящая двусмысленность есть, и она разведена заглядыванием: и — и разделитель аргументов типа, и разделитель полей варианта. Правило: и + имя + двоеточие начинает поле, всё остальное — аргумент; за типом двоеточие не идёт никогда.

Оговорка та же, что у функций-значений, и по той же причине: правило репозитория — ни одна программа, включая stdlib, не вправе использовать полиморфизм, пока self/ его не понимает. Выяснилось это дорого: файл-пример с полиморфизмом уронил три теста неподвижной точки.

Поручение и план: как чистый язык читает файл

Здесь стоит остановиться, потому что это самое неожиданное решение в языке.

flang ходит в сеть и читает файлы, не перестав быть чистым. Работает это так: вариант «Прочитать файл» с путь равным "адрес.txt" ничего не читает. Оно строит описание действия — обычное значение обычной суммы типов. Читает файл хозяин: среда, в которую напечатан модуль.

Язык вводит три суммы сам, и это не выбор автора программы, а контракт между языком и хозяином:

тип «Поручение»            // что программа просит сделать
  вариант «Прочитать файл» содержит путь: строка
  вариант «Записать файл» содержит путь: строка, содержимое: строка
  вариант «Запросить» содержит способ: строка, адрес: строка, тело: строка
  вариант «Текущее время»
  вариант «Случайное число»

Плюс «Отклик» — что хозяин принёс обратно, и «Продолжение» — что программа решила делать дальше. Пять поручений, и набор закрыт. Это утверждение, а не текущее состояние: каждое поручение обязан уметь исполнить каждый хозяин, включая напечатанного в C, и шестое пришлось бы либо писать восемь раз, либо объявлять «в некоторых целях не работает».

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

Само объявление — три строки и одно новое слово, план:

план «Сходить по ссылке»
  состояние «Ход»
  начинает с «Начать»
  обрабатывает «Дальше»

состояние, начинает с и обрабатывает уже были ключевыми — теми же тремя словами объявляется процесс, и контракт настаивает, что это не экономия слов, а утверждение: план и процесс устроены одинаково.

Почему именно план, а не шаг: проверено grep-ом по всем .flang и .fts репозитория на голое, не закавыченное вхождение — план встречается 0 раз, plan 0 раз, а шаг семь раз, и ключевое слово сломало бы два файла, к вводу-выводу отношения не имеющих.

Проверяемость без единого эффекта

Вот ради чего всё это устроено так. Описание действия сравнивается с ожидаемым описанием, значит функция с вводом-выводом проверяется обычным примером:

  пример «Первым делом читается файл с адресом»
    дано ход равно вариант «Читаем адрес»
    дано отклик равно вариант «Пока ничего»
    ожидается вариант «Сделать» с поручение равным (вариант «Прочитать файл» с путь равным "адрес.txt") и потом равным (вариант «Идём в сеть»)

Ни файла, ни сокета при этом нет. Отсюда два свойства, которые обычно теряются вместе с чистотой, и оба мы проверили запуском на flang/examples/io/link-report.flang — программе, которая читает файл, ходит в сеть и пишет отчёт:

$ node flang/bin/flang.mjs check flang/examples/io/link-report.flang --pretty
{ "valid": true, "module": "Отчёт о ссылке",
  "functions": [ { "name": "Начать", "total": true }, { "name": "Дальше", "total": true },
                 { "name": "После чтения", "total": true }, { "name": "После ответа", "total": true },
                 { "name": "После записи", "total": true }, { "name": "Первая строка", "total": true },
                 { "name": "Строка отчёта", "total": true } ], … }

$ node flang/bin/flang.mjs test flang/examples/io/link-report.flang
{ "valid": true, "total": 10, "passed": 10, "failed": 0, … }

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

Печать при этом работает во всех восьми целях, и это мы тоже проверили: emit на этой программе даёт код для всех восьми, а напечатанный C собирается под -std=c99 -Wall -Wextra -Werror -pedantic и отвечает тем же описанием поручения, что интерпретатор:

$ echo '{"fn":"Дальше","args":[{"v":"Читаем адрес","f":[]},{"v":"Пока ничего","f":[]}]}' | ./flang_cli
{"ok":true,"value":{"v":"Сделать","f":[["поручение",{"v":"Прочитать файл","f":[["путь",{"s":"адрес.txt"}]]}],
                                        ["потом",{"v":"Идём в сеть","f":[]}]]}}

Причина простая: план — это две обычные функции и три обычные суммы, а бэкенды печатают их как всё остальное. Цикла хозяина и исполнителей поручений при этом нет в семи целях из восьми — сделан только Node. Это записано в контракте отдельной таблицей, а не забыто.

Почему это не монада — и что мы за это отдали

Контракт называет разницу одной фразой:

монада прячет продолжение в замыкании — «что делать с ответом»; здесь продолжение — ЗНАЧЕНИЕ объявленного автором типа.

То есть дефункционализация монады, сделанная руками. Отдано за это четыре вещи, и все четыре видны в коде: связывание не является выражением (каждая точка ожидания — отдельный объявленный вариант, в link-report.flang их четыре); тип результата поручения не отслеживается («Отклик» — одна закрытая сумма на все пять поручений); продолжение хранится в поле типа любое; законов монады нет, потому что нет монады.

Граница честности, названная в самом контракте

Сам HTTP пишется не на flang. На flang пишется то, что запрос описывает и что с ответом делать. Обещать, что на языке без эффектов появится сокет, было бы обманом — так и записано.

Процесс и надзор: и то, и другое печатается в настоящую BEAM

Та же дисциплина, тот же вид объявления:

процесс «Работник»
  состояние «Счёт»
  начинает с «нулевой счёт»
  принимает «Задача»
  обрабатывает «шаг работника»

надзор «Цех»
  процесс «Работник» стратегия «перезапустить»
  порог отказов 2 за 5000 миллисекунд иначе «остановить»

Обработчик — обычная чистая функция: получает состояние и сообщение, возвращает новое состояние и список описанных действий. Действия не выполняются, а описываются — по той же причине, что и поручения плана.

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

Проверяется всё это прогонами, объявленными в том же файле:

прогон «упал и вернулся ровно к началу»
  семя 1
  дано «Работник» принимает (вариант «прибавить» с «сколько» равным 4)
  дано «Работник» принимает (вариант «подавиться» с «почему» равным "испортился вход")
  ожидается «Работник» стратегия «перезапустить» 1 раз
  ожидается «Работник» равен (запись «Счёт» с «всего» равным 0)
  ожидается «Учётчик» равен (запись «Записи» с «строки» равным ["4"])

flang test flang/conc/examples/supervision.flang даёт 5 из 5: два примера обработчиков и три прогона. Перезапуск здесь возвращает состояние к начальному значению, и дочищать после отказа нечего — состояние неизменяемо, наполовину применённой мутации не бывает, потому что мутаций нет вовсе.

Что до печати — она разобрана в главе «Кодогенерация», потому что там случилось самое интересное: сверка не по значению, а по множеству допустимых исходов.

Чего в синтаксисе нет

Коротко, чтобы не искать: нет циклов, нет присваивания, нет объявления переменной вне пусть, нет исключений и try, нет логических операторов, нет лямбд и замыканий, нет каррирования и частичного применения, нет литерала записи, нет монады и формы в монаде. Полный проверенный перечень — глава «Чего в языке пока нет».

Три пункта из этого списка за сутки ушли и здесь больше не стоят: функции как значения, обобщённые типы и вызов функции без аргументов. Это не оговорка о темпе, а предупреждение о жанре: список того, чего нет, стареет быстрее всего остального в тексте про язык.

Дальше — глава «Система типов», про то, как всё это проверяется.

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

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

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

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