Полярные типы данных (Ramifications) в языке Эдва (Edva): единая замена для bool, Option и Result
В большинстве языков программирования понятия «истина/ложь», «значение есть/значения нет» и «успех/ошибка» живут в разных мирах. Для первого существует примитив bool и конструкции if/else, для второго — Option<T> (Maybe a), для третьего — Result<T, E> (Either a b). У каждого из них свои конструкторы (true/false, Some/None, Ok/Err) и свой набор комбинаторов.
Системный язык программирования Edva, спроектированный без единого ключевого слова, сводит все бинарные исходы к единому примитиву на уровне системы типов — алгебраическим разветвлениям (Algebraic Ramifications, или коротко — Rams).
Четыре формы полярного типа и представление в памяти
В Эдве любой бинарный исход описывается типом с двумя полюсами: положительным (левым) и отрицательным (правым). В зависимсти от того, несут ли полюса полезную нагрузку, существует ровно четыре формы:
|
Форма типа |
Положительный полюс |
Отрицательный полюс |
Что заменяет в классических языках |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Статус выполнения ( |
|
|
<+ value -> |
<- error +> |
|
На уровне LLVM IR значения полярных типов не требуют аллокаций в куче или сложных вложенных тегов — они укладываются в компактное необёрнутое (unboxed) представление { i1, i64 } (бит полярности плюс полезная нагрузка), а в случаях, когда тип уже представляет собой указатель, не нужен даже бит полярности, поскольку второй полюс можно выразить нулевым адресом, и представление сокращается до { i64 }.
Конструкторы полюсов
Для явного создания значений полярного типа используются симметричные символьные конструкторы:
-
Положительный полюс:
<+>,<+ val +>,<++>,<+ val ->(пустой успех или успех со значениемval). -
Отрицательный полюс:
<->,<-->,<- err ->,<- err +>(пустая неудача или неудача со значениемerr).
Взаимодействие с оператором выбора и стражами | и […]
Поскольку в Edva нет ключевых слов if, else, match или switch, управление потоком строится на операторе выбора, который выглядит так:
значение [страж ветвь]+
Для обычных значений стражи в квадратных скобках проверяют равенство или предикат, а | выступает веткой по умолчанию:
classify = score => score [100] "perfect" [99, 98] "almost perfect" [> 90] "distinction" [> 50] "pass" | "fail"
Когда же слева от оператора выбора | оказывается полярный тип (ramification), срабатывает позиционное сопоставление:
-
первая ветка после
|обрабатывает положительный полюс (+), -
вторая ветка после
|обрабатывает отрицательный полюс (-).
Если полюс несет значение, внутри ветки к нему можно обратиться через позиционный плейсхолдер _ (или секцию оператора):
// Функция возвращает < Int | > (аналог Option<Int>)res = find_target(10, 7)// Первая ветка берет значение из <+ val +>, вторая срабатывает на <-->res | print_int(_) | print $ "Not found"
Никаких if let Some(x) = res или match res { Some(x) => ..., None => ... }: полярность значения напрямую выбирает нужную ветвь выражения.
Циклы как генераторы полярных типов (>—)
В императивных языках поиск элемента в цикле обычно требует мутабельного флага found = false или досрочного return из функции. В Edva циклы (@) являются выражениями, которые с помощью оператора выхода >-- естественным образом возвращают < T | >:
// Цикл ищет цель в диапазоне 0..count и возвращает < Int | >find_target: Int, Int => < Int | >find_target = count, target => count @ i => i [target] --> // если i == target, прерываем цикл (break) >-- target // значение при срабатывании -->
Механика работы >--:
-
Если внутри цикла сработал оператор прерывания
-->, блок после>--вычисляет результирующее значение и автоматически упаковывает его в положительный полюс<+ target +>. -
Если цикл прошел до конца без прерываний, выражение целиком возвращает отрицательный полюс
<-->.
Полученный результат тут же готов к передаче в оператор выбора | или дальше по конвейеру.
Композиция и обработка ошибок: $>> и —!
Когда функции возвращают < T | E > или < T | >, Edva предлагает два способа композиции без ручного разбора веток: конвейерное монадическое связывание и постфиксное пробрасывание.
1. Монадический bind ($>>)
Оператор $>> пропускает положительный полюс через цепочку функций, а при возникновении отрицательного полюса (<- err -> или <-->) коротко замыкает вычисление и пробрасывает его на выход:
find_user: String => < User | Error >find_user = id => query_db(id) $>> validate $>> (<+ _ +>)// выражение с подчёркиванием в скобках это лямбда, эквиалентно x => <+ x +>
Если query_db или validate вернут отрицательный полюс с ошибкой, вся цепочка немедленно завершится с этим же отрицательным полюсом типа Error: <- error ->.
2. Постфиксная распаковка и ранний возврат (—!)
Для последовательного кода, где нужно распаковать несколько полярных значений подряд, используется постфиксный оператор --!:
load_config: String => < Config | Error >load_config = path => raw = io::read_file(path)--! cfg = json::parse(raw)--! <+ cfg +>
Если io::read_file(path) возвращает <+ content +>, оператор --! извлекает content и связывает его с raw. Если же вернулся <- err ->, выполнение load_config прерывается (по аналогии с тем, как --> прерывает цикл), возвращая ошибку вызывающей стороне.
Итог: единая алгебра вместо зоопарка типов
За счет объединения булевой логики, опциональных типов и результатов с ошибками в четыре формы < ... | ... >, Edva избавляется от дублирования концепций:
-
Вместо отдельных
bool,OptionиResult— единый параметризуемый тип{ i1, i64 }. -
Вместо
if/else,match,.unwrap_or()иif let— единый оператор выбора|с позиционными ветками. -
Вместо ручных флагов выхода из циклов — оператор
>--, связывающий управление циклом напрямую с системой типов.
Ссылки
-
репозиторий Edva https://github.com/edva-lang/edva
-
документация Edva https://edva-lang.github.io
-
экосистема Edva (примеры файлового менеджера, библиотек и компиляции в WASM)
ссылка на оригинал статьи https://habr.com/ru/articles/1104020/