Skip to content

RFC-010: Единый синтаксис типов — модель name: type = value ​

Резюме ​

Настоящий RFC предлагает предельно минималистичную единую модель синтаксиса типов: всё есть name: type = value.

YaoXiang имеет единственную форму объявления:

identifier : type = expression

где type может быть любым выражением типа, а expression — любым выражением значения. Нет fn, нет struct, нет trait, нет impl, нет строчного ключевого слова type (но есть Type как ключевое слово мета-типа).

Ключевое решение: Type сам по себе является родовым типом. (T: Type) -> Type означает «тип, принимающий параметр типа T».

ПонятиеЗапись в коде
Переменнаяx: Int = 42
Функцияadd: (a: Int, b: Int) -> Int = a + b
Record typePoint: Type = { x: Float, y: Float }
ИнтерфейсDrawable: Type = { draw: (Surface) -> Void }
Родовой типList: (T: Type) -> Type = { data: Array(T), length: Int }
Родовой типMap: (K: Type, V: Type) -> Type = { keys: Array(K), values: Array(V) }
МетодPoint.draw: (p: Point, s: Surface) -> Void = ...
Point.draw = draw[0]
Родовая функцияmap: (T: Type, R: Type) -> ((list: List(T), f: (x: T) -> R) -> List(R))

Type — единственное ключевое слово мета-типа в языке.

Пространство имён vs привязка метода: префикс Type.name означает лишь принадлежность к пространству имён и ничего более. Он не вызывает никакой неявной привязки. Чтобы заработал синтаксис вызова через . вроде p.draw(screen), требуется явная привязка: Point.draw = draw[0]. Подробности см. в разделе «Пространства имён и привязка методов» ниже. Он используется для разметки уровня типа; компилятор автоматически различает Type0, Type1, Type2... прозрачно для пользователя.

yaoxiang
// Основной синтаксис: единый + различимый

// Переменная
x: Int = 42

// Функция (имена параметров в сигнатуре)
add: (a: Int, b: Int) -> Int = a + b

// Record type
Point: Type = {
    x: Float,
    y: Float,
    draw: (Surface) -> Void,
    serialize: () -> String
}

// Интерфейс (по сути record type, все поля которого — функции)
Drawable: Type = {
    draw: (Surface) -> Void,
    bounding_box: () -> Rect
}

Serializable: Type = {
    serialize: () -> String
}

// Определение метода (синтаксис Type.method)
Point.draw: (self: Point, surface: Surface) -> Void = {
    surface.plot(self.x, self.y)
}

Point.serialize: (self: Point) -> String = {
    "Point(${self.x}, ${self.y})"
}

// Родовой тип ((T: Type) -> Type = родовой тип, принимающий параметр типа)
List: (T: Type) -> Type = {
    data: Array(T),
    length: Int
}

Map: (K: Type, V: Type) -> Type = {
    keys: Array(K),
    values: Array(V)
}

// Использование
p: Point = Point(1.0, 2.0)
p.draw(screen)           // синтаксический сахар → Point.draw(p, screen)
s: Drawable = p           // структурная подтипизация: Point реализует Drawable
drawables: List(Drawable) = [p, r]
process_all(drawables)

Мотивация ​

Зачем нужна эта возможность? ​

Текущая система типов содержит несколько разрозненных понятий:

  • синтаксис объявления переменных;
  • синтаксис определения функций;
  • синтаксис определения типов (другой синтаксис);
  • синтаксис определения интерфейсов;
  • синтаксис привязки методов.

Между этими понятиями отсутствует единство, что приводит к фрагментации синтаксиса и высокой стоимости обучения.

Цели проектирования ​

  1. Предельное единство: одно правило синтаксиса покрывает все случаи.
  2. Краткость и элегантность: симметричная эстетика name: type = value.
  3. Без новых ключевых слов: повторное использование существующих элементов синтаксиса.
  4. Теоретическая элегантность: сам тип также является значением типа Type.
  5. Дружелюбность к дженерикам: бесшовная интеграция с системой дженериков (RFC-011).

Интеграция с системой дженериков ​

Единая синтаксическая модель RFC-010 органически сочетается с дизайном системы дженериков RFC-011: параметры дженериков бесшовно вписываются в единую модель:

yaoxiang
// Базовые дженерики (RFC-011 Phase 1)
List: (T: Type) -> Type = { data: Array(T), length: Int }

// Родовая функция (синтаксис RFC-023: позиция Type в сигнатуре можно опустить,
// на стороне вызова выводится автоматически)
map: (: Type, R: Type) -> (( list: List(T), f: (T) -> R) -> List(R)) = ...

// Ограничения типа (RFC-011 Phase 2)
clone: (value: T) -> T = value.clone()  // ограничение T: Clone переносится через тип параметра

// Const-дженерики (RFC-011 Phase 4)
Array: (T: Type, N: Int) -> Type = { data: Array(T, N), length: N }

Зависимости:

  • RFC-011 Phase 1 (базовые дженерики) — сильная зависимость для RFC-010.
  • Без базовых дженериков примеры дженериков в RFC-010 не компилируются.
  • Рекомендация: RFC-011 Phase 1 реализуется одновременно с RFC-010.

Предложение ​

Ключевой принцип: конструктор типа vs функция/переменная ​

Это ключевой выбор дизайна, определяющий правила устранения неоднозначности синтаксиса:

ЗаписьЗначениеПравило
x: Type = ...Конструктор типаЯвное объявление : Type → обязательно тип
f = ...Функция или переменнаяБез : Type → HM самостоятельно выводит функцию/переменную

Почему так устроено?

Сам синтаксис { ... } неоднозначен:

  • { x: Float, y: Float } может быть литералом типа (record type)
  • { a = 1 + 1 } может быть блоком кода (исполняемый оператор, возвращающий Void)

Правила устранения неоднозначности:

  • Есть : Type → принудительно разбирается как конструктор типа, { ... } — литерал типа
  • Нет : Type → HM самостоятельно разбирает { ... } как блок кода, выводит функциональный тип
yaoxiang
# ✅ Конструктор типа: есть : Type
Point: Type = { x: Float, y: Float }

# ✅ Функция: нет : Type, HM выводит () -> Void
main: () -> Void = { println("Hello") }

# ❌ Ошибка: нет : Type, компилятор не может разобрать { ... } как тип
Point = { x: Float, y: Float }  // HM выводит функцию, а не тип!

Единая модель: identifier : type = expression

├── Переменная
│   └── x: Int = 42
│
├── Функция
│   └── add: (a: Int, b: Int) -> Int = a + b  # без : Type, HM выводит функцию
│
├── Record type
│   └── Point: Type = { x: Float, y: Float }  # должен вернуть: Type
│
├── Интерфейс
│   └── Drawable: Type = { draw: (Surface) -> Void }  # должен вернуть: Type
│
├── Родовой тип
│   └── List: (T: Type) -> Type = { data: Array(T), length: Int }  # должен вернуть: Type
│
├── Родовой тип (несколько параметров)
│   └── Map: (K: Type, V: Type) -> Type = { keys: Array(K), values: Array(V) }  # должен вернуть: Type
│
├── Функция в пространстве имён
│   └── draw: (p: Point, surface: Surface) -> Void = ...
│       Point.draw = draw[0]  # только после явной привязки доступен синтаксис вызова через точку
│
└── Родовая функция
    └── map: (T: Type, R: Type) -> ((list: List(T), f: (x: T) -> R) -> List(R))  # не возвращает Type, HM выводит функцию

Уровни мета-типов (внутри компилятора) ​

Внутри компилятора поддерживается иерархия вселенной level: selfpointnum (хранится в виде строки, теоретически бесконечно расширяемая).

LevelОписание
Type0Повседневные типы (Int, Float, Point)
Type1Конструкторы типов (List, Maybe)
Type2+Конструкторы высшего порядка

Пользователь никогда не видит эти цифры — только : Type.

Изоморфизм Карри–Ховарда: тип как утверждение, программа как доказательство ​

Единый синтаксис YaoXiang name: type = value выбран не произвольно — он является прямым отражением изоморфизма Карри–Ховарда (Curry–Howard correspondence). Этот изоморфизм вскрывает глубокий факт: система типов и логическая система — это две стороны одной сущности.

Логика (утверждение)Система типов (YaoXiang)Пример
Утверждение PТип TInt, Bool
Доказательство истинности PЗначение типа T42: Int, true: Bool
P → Q (импликация)Функциональный тип (P) -> Q(x: Int) -> Bool
P ∧ Q (конъюнкция)Record type { p: P, q: Q }{ x: Int, y: Bool }
∀x.P(x) (квантор всеобщности)Родовая функция (T: Type) -> ...map: (T: Type, R: Type) -> ...
P ⊕ Q (дизъюнкция)enum / tagged unionMaybe: (T: Type) -> Type = { ... }

Что означает name: type = value в рамках Карри–Ховарда:

yaoxiang
// "x: Int = 42" читается как: "существует доказательство типа Int по имени x со значением 42"
x: Int = 42

// "add: (a: Int, b: Int) -> Int = a + b" читается как:
// "существует доказательство импликации: даны доказательства Int a и b, можно построить доказательство Int"
add: (a: Int, b: Int) -> Int = a + b

// "Point: Type = { x: Float, y: Float }" читается как:
// "Point — это утверждение, доказательство которого требует одновременно доказательство Float x и доказательство Float y"
Point: Type = { x: Float, y: Float }

Почему это важно?

  1. Логическая непротиворечивость = типобезопасность: если система типов разрешает построить значение типа T без какого-либо легитимного представления в рантайме, это подобно тому, как логика разрешает доказать ложное утверждение — система рушится. Карри–Ховард говорит нам: типобезопасный язык естественным образом является непротиворечивой логической системой.

  2. Иерархия вселенных — необходимое условие: как подробно описано ниже, разрешение Type: Type (то есть «тип типа тоже тип») приводит к парадоксу Рассела (в теории типов — парадоксу Жирара). Слои Type₀ : Type₁ : Type₂ : ... в YaoXiang гарантируют, что каждый тип принадлежит строго своему уровню, образуя бесконечно восходящую цепочку, что фундаментально устраняет парадоксы. Это означает, что система типов YaoXiang в смысле Карри–Ховарда логически непротиворечива.

  3. Теоретическое обоснование единого синтаксиса: name: type = value способен одной формой синтаксиса покрыть переменные, функции, типы, интерфейсы и дженерики именно потому, что в рамках Карри–Ховарда это всё одна сущность — предоставление доказательств для утверждений. Переменная — свидетельство утверждения, функция — свидетельство импликации, record — свидетельство конъюнкции, дженерики — свидетельство квантора всеобщности. Единый синтаксис — не случайность дизайна, а естественное следствие изоморфизма Карри–Ховарда.

Дополнительное чтение: Wadler, P. (2015). "Propositions as Types." Communications of the ACM, 58(12), 75–84. Эта статья доступно объясняет историю и значение изоморфизма Карри–Ховарда.

Определение синтаксиса ​

1. Объявление переменной ​

yaoxiang
// Базовый синтаксис
x: Int = 42
name: String = "Alice"
flag: Bool = true

// Вывод типа (можно опустить)
y = 100  // выводится как Int

2. Определение функции ​

Значение блока = хвостовое выражение, return осуществляет выход из функции (тип Never) — подробности см. в RFC-010a.

yaoxiang
// Форма с одним выражением
add: (a: Int, b: Int) -> Int = a + b
greet: (name: String) -> String = "Hello, ${name}!"

// Форма блока кода: значение — хвостовое выражение
process: (x: Int) -> Int = {
    a = x * 2
    b = a + 1
    b                    // хвостовое выражение → значение блока
}

// Многострочный блок кода
calc: (x: Float, y: Float, op: String) -> Float = {
    match op {
        "+" -> x + y,
        "-" -> x - y,
        _ -> 0.0
    }                    // match как хвостовое выражение
}

// Функция Void: хвостовое выражение имеет тип Void
print: (msg: String) -> Void = {
    console.write(msg)   // console.write : Void
}

Правила возврата ​

Значение блока = хвостовое выражение (единственный выход):

ЗаписьЗначение
= expr (без фигурных скобок)expr
= { ...; e } (с фигурными скобками)хвостовое выражение e
= { ...; s } (последним идёт оператор/присваивание)Void (значение присваивания — Void)
= {} (пустой блок)Void

Семантика return: нелокальный выход, выход из ближайшей границы функции (а не «возврат в блок»), тип — Never. Never <: T для любого типа (принцип взрыва), поэтому return может встречаться в любой позиции возврата.

yaoxiang
# Одно выражение: прямое возвращение значения
add: (a: Int, b: Int) -> Int = a + b

# Блок кода: значение — хвостовое выражение
process: (x: Int) -> Int = {
    a = x * 2
    b = a + 1
    b
}

# Досрочный возврат: return проходит сквозь блок и выходит из функции (тип Never)
factorial: (n: Int) -> Int = {
    if n <= 1 { return 1 }
    n * factorial(n - 1)     # хвостовое выражение
}

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

Семантика {}: вычислительная единица, управляемая зависимостями ​

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

Основные правила:

  • операторы присваивания внутри {} автоматически упорядочиваются по зависимостям, а не по порядку записи;
  • готовые зависимости выполняются немедленно, при отсутствии — блокируются в ожидании;
  • значение блока = хвостовое выражение (см. правила возврата); return — нелокальный выход типа Never, осуществляющий выход из функции.
yaoxiang
# Управление зависимостями: b зависит от a, компилятор автоматически упорядочивает
result: Int = {
    b = a + 1      # зависит от a → автоматически после a
    a = 10         # без зависимостей → может выполниться первым
    b              # хвостовое выражение → значение блока равно 11
}

Отличие от одночного выражения: = expr (без фигурных скобок) — простая привязка, возвращающая значение напрямую; = { ... } (с фигурными скобками) вводит контекст вычислений, управляемый зависимостями, допускает несколько операторов, и его значение задаётся хвостовым выражением.

Блок spawn ​

spawn { ... } — единственная примитива параллелизма в YaoXiang. Она использует семантику {}, управляемую зависимостями, для автоматического распараллеливания:

  • непосредственные дочерние присваивания внутри spawn { ... } автоматически создают параллельные задачи;
  • задачи, для которых зависимости удовлетворены, выполняются конкурентно немедленно;
  • вызывающая сторона блокируется до завершения всех дочерних задач.
yaoxiang
result = spawn {
    a = fetch_data("url1")    # задача 1
    b = fetch_data("url2")    # задача 2 (нет зависимости от a, выполняется параллельно)
    c = process(a, b)         # зависит от a, b → ожидает их завершения
    c                         # хвостовое выражение → значение spawn
}
// вызывающая сторона блокируется здесь до завершения всех задач внутри spawn

Подробное определение: полная семантика spawn, правила создания задач и модель блокировки см. в 008-runtime-concurrency-model.md.

Блок unsafe ​

unsafe { ... } используется для определения непрозрачных типов и работы с сырыми указателями. Он использует семантику вычисления {} для передачи определения типа во внешнюю область видимости (выход значения — хвостовое выражение):

Основные правила:

  • внутри unsafe {} можно определять типы и работать с сырыми указателями;
  • хвостовое выражение даёт значение unsafe {} (определение типа передаётся во внешнюю область видимости);
  • возвращённый тип доступен за пределами unsafe {};
  • для доступа к полям типа требуются права unsafe.
yaoxiang
# Определение непрозрачного типа внутри блока unsafe
SqliteDb = unsafe {
    SqliteDb: Type = {
        handle: *Void  # сырой указатель
    }
    SqliteDb           # хвостовое выражение → значение блока unsafe
}

# SqliteDb доступен за пределами блока unsafe
db = sqlite3_open("test.db")

# ❌ Ошибка компиляции: для доступа к полю handle нужны права unsafe
handle = db.handle

# ✅ Через вызов метода
db.close()

Подробное определение: полная семантика unsafe, определение FFI-типов и привязка методов см. в ffi.md.

3. Определение типа ​

Определение типа — ядро единого синтаксиса YaoXiang, включающее поля, значения по умолчанию, привязанные методы и реализации интерфейсов:

Базовые типы ​

Record type: список полей, типы полей могут быть любыми выражениями типа.

yaoxiang
Point: Type = {
    x: Float,
    y: Float
}

Поля со значениями по умолчанию: поля могут иметь значения по умолчанию, необязательные при конструировании.

yaoxiang
Point: Type = {
    x: Float = 0,
    y: Float = 0
}

Использование:

yaoxiang
Point() → Point(x=0, y=0)
Point(x=1) → Point(x=1, y=0)
Point(x=1, y=2) → Point(x=1, y=2)

Поля без значений по умолчанию: должны быть предоставлены при конструировании.

yaoxiang
Point2: Type = {
    x: Float,
    y: Float
}

Использование:

yaoxiang
Point2(x=1, y=2) //✓
Point2() //✗
Point2(x=1) //✗
Встроенные типы ​

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

  1. Ключевые слова (отдельные токены парсера) — управляющие конструкции и ключевые слова объявления, такие как if, match, pub, return.
  2. Литеральные зарезервированные слова (отдельные токены парсера) — true, false, void, Type, не могут использоваться как обычные идентификаторы.
  3. Имена встроенных типов (предварительно зарегистрированные в type checker) — парсер рассматривает их как обычные идентификаторы, разбором занимается проверщик типов. Не являются зарезервированными словами, могут быть затенены (не рекомендуется).

Различие между void (строчное, литеральное зарезервированное слово) и Void (прописное, имя встроенного типа): void — литерал значения (равен единственному значению Unit), Void — имя типа (равен типу Unit, логически ⊤). let x: Void = void допустимо.

Предопределённые имена встроенных типов:

ТипЛогический аналогОписание
Never⊥ (ложь/пустой тип)Нулевой набор конструкторов, никакое значение не может населять этот тип. Означает «невозможно» — расходимость, паника, мёртвый код. Never <: T для любого T (принцип взрыва). Функция, возвращающая Never, никогда не возвращается нормально. Не ключевое слово, а имя встроенного типа.
Void⊤ (истина/Unit)Ровно один обитатель (по умолчанию значение void). x: Void = <по умолчанию> допустимо. Единица для типа-суммы, соответствующая единице для типа-произведения — Void — это тип-произведение с нулём полей (Unit), Never — тип-сумма с нулём вариантов.
Int—Целое число со знаком
Float—Число с плавающей точкой
Bool—Логическое значение: true / false
Char—Unicode-символ
String—Строка
Привязка методов ​

Способ 1: прямая привязка внешней функции внутри тела определения типа

yaoxiang
distance: (a: Point, b: Point) -> Float = { ... }
Point: Type = {
    x: Float = 0,
    y: Float = 0,
    distance = distance[0]           // привязка к позиции 0, после каррирования method: (b: Point) -> Float
}
// Вызов: p1.distance(p2) → distance(p1, p2)

Способ 2: анонимная функция + позиционная привязка

yaoxiang
Point: Type = {
    x: Float = 0,
    y: Float = 0,
    distance: ((a: Point, b: Point) -> Float)[0] = ((a, b) => {
        dx = a.x - b.x
        dy = a.y - b.y
        (dx * dx + dy * dy).sqrt()      # хвостовое выражение
    })
}
// Синтаксис: ((params) => body)[position]
// Вызов: p1.distance(p2) → distance(p1, p2)
Реализация интерфейса ​

Имя интерфейса записывается внутри тела типа, компилятор автоматически проверяет реализацию

yaoxiang
Drawable: Type = {
    draw: (Surface) -> Void,
    bounding_box: () -> Rect
}

Serializable: Type = {
    serialize: () -> String
}

Point: Type = {
    x: Float,
    y: Float,
    Drawable,          // реализация интерфейса Drawable
    Serializable      // реализация интерфейса Serializable
}
Определение интерфейса ​

Интерфейс = record type, все поля которого — функции

yaoxiang
Drawable: Type = {
    draw: (Surface) -> Void,
    bounding_box: () -> Rect
}

Serializable: Type = {
    serialize: () -> String
}

// Пустой тип / пустой интерфейс
EmptyType: Type = {}
Empty: Type = {}
Определение функции в пространстве имён ​

Префикс Type.name означает лишь принадлежность к пространству имён и ничего более. Он не вызывает никакой неявной привязки.

yaoxiang
// Функция в пространстве имён: обычная функция в пространстве имён Point
Point.draw: (p: &Point, surface: Surface) -> Void = {
    surface.plot(p.x, p.y)
}

Point.serialize: (p: &Point) -> String = {
    "Point(${p.x}, ${p.y})"
}

// Вызов: это обычный вызов функции
Point.draw(p, screen)
Point.serialize(p)

Замечание: self — не ключевое слово, а лишь устоявшееся имя параметра. Запись p, this, x даёт совершенно тот же эффект. Компилятор смотрит на тип, а не на имя параметра.

Привязка метода (единственный способ) ​

Чтобы заработал синтаксис вызова метода через . вроде p.draw(screen), требуется явная привязка. Синтаксис [position] — единственный механизм привязки функции в качестве «метода» (подробный синтаксис см. в RFC-004).

yaoxiang
// Определение функции
draw: (p: &Point, surface: Surface) -> Void = {
    surface.plot(p.x, p.y)
}

// Явная привязка — только после неё доступен синтаксис p.draw(screen)
Point.draw = draw[0]   // параметр на позиции 0 (&Point) заполняется вызывающей стороной

// Использование
p.draw(screen)          // синтаксический сахар → draw(&p, screen)
Point.draw(p, screen)   // обе формы вызова эквивалентны

// Без [0] = без привязки. Point.draw — обычный псевдоним функции, без синтаксиса .
Point.draw = draw       // без привязки: можно только Point.draw(p, screen)

Поведение по умолчанию: без [n] = ни один параметр не привязывается. Пользователь обязан явно решить, какие параметры заполняются вызывающей стороной.

Многопозиционная привязка:

yaoxiang
// Привязка нескольких позиций (автоматическое каррирование)
Point.transform = transform_points[0, 1]
// Вызов: p1.transform(p2)(2.0) → transform_points(p1, p2, 2.0)

Обратная операция (метод → обычная функция):

yaoxiang
// Извлечение функции из привязки
draw_point: (p: &Point, surface: Surface) -> Void = Point.draw

4. Композиция интерфейсов ​

yaoxiang
// Композиция интерфейсов = пересечение типов
DrawableSerializable: Type = Drawable & Serializable

// Использование пересечения типов
process: (T: Drawable & Serializable) -> ((item: T, screen: Surface) -> String) = {
    item.draw(screen)
    item.serialize()
}

5. Родовые типы ​

yaoxiang
// Базовые дженерики (RFC-011 Phase 1)
List: (T: Type) -> Type = {
    data: Array(T),
    length: Int,
    push: (T:Type)-((self: List(T), item: T) -> Void),
    get: (T:Type)->((self: List(T), index: Int) -> Maybe(T))
}

// Конкретизация (синтаксис RFC-023)
IntList: Type = List(Int)

IntList.push = {
    self.data.append(item)
    self.length = self.length + 1
}

List.push = (type: Type) -> {
    (self: List(type), item: type) -> {
        self.data.append(item)
        self.length = self.length + 1
    }
}

IntList.push(Int)(self, item)  // пример вызова

// Родовой метод (синтаксис RFC-023: параметры типа автоматически выводятся в точке вызова)
List.push: (self: List(T), item: T) -> Void = {
    self.data.append(item)
    self.length = self.length + 1
}

List.get: (self: List(T), index: Int) -> Maybe(T) = {
    if index >= 0 and index < self.length {
        Maybe.Just(self.data[index])
    } else {
        Maybe.Nothing
    }
}

6. Синтаксис вызова дженериков ​

Для вызова родовых типов и родовых функций единообразно используется синтаксис (). [] нигде в контексте дженериков не используется.

Основные правила:

  1. () делает всё применение: применение типа, вызов функции, конструирование значения — всё через ().
yaoxiang
# Аннотация типа
numbers: List(Int) = List(1, 2, 3)

# Пустой контейнер: T приходит слева
empty: List(Int) = List()

# Вызов родовой функции — типы автоматически текут из аргументов
strings = map(numbers, f)
// T=Int приходит из numbers: List(Int)
// R=String приходит из f: (Int) -> String
  1. Type слева, значение справа: name: type = value — параметры Type объявляются слева, справа всегда конкретные значения. Для пустого контейнера List() значение T должно приходить из аннотации типа слева.

  2. Информация о типе пишется лишь раз — при объявлении параметров компилятор проносит её дальше:

yaoxiang
numbers: List(Int) = List(1, 2, 3)  // Int пишется один раз слева
f: (Int) -> String = (x) => x.to_string()
strings = map(numbers, f)   // T=Int, R=String автоматически берутся из типов numbers и f
  1. Конструирование значения выводит тип из элементов:
yaoxiang
x = List(1, 2, 3)       // выводится как List(Int)
y = List("a", "b")      // выводится как List(String)
z = List()              // ❌ ошибка компиляции: не удаётся вывести T
z: List(Int) = List()   // ✅ T=Int приходит из аннотации слева
  1. Псевдонимы типов:
yaoxiang
IntList: Type = List(Int)
StringToInt: Type = (String) -> Int
Matrix3x3: Type = Matrix(Float, 3, 3)

Сравнение со старым синтаксисом: List[Int] → List(Int), List[Int]() → List(), List[Int](1,2,3) → List(1,2,3). Старый синтаксис дженериков [] полностью удалён. [] используется только для литералов массивов/списков и доступа по индексу.

Примеры ​

Полный пример ​

yaoxiang
// ======== 1. Определение интерфейса ========
// Интерфейс = record type, все поля которого — функциональные типы
// В интерфейсе параметр self не нужен — интерфейс задаёт только «сигнатуру функций без позиции вызывающего»

Drawable: Type = {
    draw: (surface: Surface) -> Void,
    bounding_box: () -> Rect
}

Serializable: Type = {
    serialize: () -> String
}

Transformable: Type = {
    translate: (dx: Float, dy: Float) -> Transformable,  // возвращает тип интерфейса, конкретная реализация возвращает свой собственный тип
    scale: (factor: Float) -> Transformable
}

// ======== 2. Определение типа ========

Point: Type = {
    x: Float,
    y: Float,
    Drawable,
    Serializable,
    Transformable
}

Rect: Type = {
    x: Float,
    y: Float,
    width: Float,
    height: Float,
    Drawable,
    Serializable,
    Transformable
}

// ======== 3. Реализация методов (обычные функции + явная привязка) ========

// Определение функций (self — лишь устоявшееся имя, не ключевое слово)
draw: (p: &Point, surface: Surface) -> Void = {
    surface.plot(p.x, p.y)
}

bounding_box: (p: &Point) -> Rect = {
    Rect(p.x - 1, p.y - 1, 2, 2)
}

serialize: (p: &Point) -> String = {
    "Point(${p.x}, ${p.y})"
}

translate: (p: &Point, dx: Float, dy: Float) -> Point = {
    Point(p.x + dx, p.y + dy)
}

scale: (p: &Point, factor: Float) -> Point = {
    Point(p.x * factor, p.y * factor)
}

distance: (p1: &Point, p2: &Point) -> Float = {
    dx = p1.x - p2.x
    dy = p1.y - p2.y
    (dx * dx + dy * dy).sqrt()
}

// Явная привязка — только после неё доступен синтаксис вызова через точку
Point.draw = draw[0]
Point.bounding_box = bounding_box[0]
Point.serialize = serialize[0]
Point.translate = translate[0]
Point.scale = scale[0]
Point.distance = distance[0]

// Методы Rect аналогично
draw: (r: &Rect, surface: Surface) -> Void = {
    surface.draw_rect(r.x, r.y, r.width, r.height)
}
Rect.draw = draw[0]

bounding_box: (r: &Rect) -> Rect = r
Rect.bounding_box = bounding_box[0]

serialize: (r: &Rect) -> String = {
    "Rect(${r.x}, ${r.y}, ${r.width}, ${r.height})"
}
Rect.serialize = serialize[0]

translate: (r: &Rect, dx: Float, dy: Float) -> Rect = {
    Rect(r.x + dx, r.y + dy, r.width, r.height)
}
Rect.translate = translate[0]

scale: (r: &Rect, factor: Float) -> Rect = {
    Rect(r.x * factor, r.y * factor, r.width * factor, r.height * factor)
}
Rect.scale = scale[0]

// ======== 4. Использование ========

// Создание экземпляров
p: Point = Point(1.0, 2.0)
r: Rect = Rect(0.0, 0.0, 10.0, 20.0)

// Вызов метода (синтаксический сахар)
p.draw(screen)
r.draw(screen)

// Обычный вызов (прямой вызов функции)
d: Float = distance(p, Point(0.0, 0.0))

// Цепочка вызовов
p2: Point = p.translate(1.0, 1.0).scale(2.0)

// Присваивание интерфейсу
drawables: List(Drawable) = [p, r]
for d in drawables {
    d.draw(screen)
}

// Родовая функция (синтаксис RFC-023: параметры типа опускаются при вызове, выводятся автоматически)
process_all: (items: List(T)) -> Void = {
    for item in items {
        print(item.serialize())
    }
}

process_all([p, r])

Подробный дизайн ​

Алгоритм проверки интерфейса ​

rust
fn check_type_implements_interface(
    typ: &Type,
    iface: &Type
) -> Result<(), TypeError> {
    // Для каждого поля интерфейса (функционального поля)
    for (field_name, iface_field) in &iface.fields {
        // Проверяем, есть ли у типа одноимённый метод
        if let Some(method) = typ.methods.get(field_name) {
            // Проверяем совместимость сигнатуры метода
            // Поле интерфейса: (Surface) -> Void
            // Сигнатура метода: (Point, Surface) -> Void
            // Сравнение: должно совпадать после удаления параметра self
            if !method_signature_matches(method, iface_field.type_) {
                return Err(TypeError::MethodSignatureMismatch {
                    type_name: typ.name,
                    interface_name: iface.name,
                    method_name: field_name,
                });
            }
        } else {
            return Err(TypeError::MissingMethod {
                type_name: typ.name,
                interface_name: iface.name,
                method_name: field_name,
            });
        }
    }
    Ok(())
}

Прямое присваивание интерфейсу и оптимизации на этапе компиляции ​

Интерфейсный тип поддерживает прямое присваивание; компилятор автоматически выбирает оптимальную стратегию вызова на основе типа правой части:

yaoxiang
// Прямое присваивание конкретного типа → конкретный тип известен в момент компиляции, вызов без накладных расходов
d: Drawable = Circle(1)
d.draw(screen)  // после компиляции: прямой вызов circle_draw(screen), без vtable

// Возвращаемое значение функции → конкретный тип не известен в момент компиляции, используется vtable
d: Drawable = get_shape()
d.draw(screen)  // поиск метода через vtable

// Гетерогенная коллекция → используется vtable
shapes: List(Drawable) = [Circle(1), Rect(2, 3)]
for s in shapes {
    s.draw(screen)  // поиск метода через vtable
}

Стратегии оптимизации на этапе компиляции:

СценарийРезультат выводаСпособ вызова
d: Drawable = Circle(1)Конкретный тип CircleПрямой вызов (нулевые накладные расходы)
d: Drawable = get_shape()Неизвестноvtable
shapes: List(Drawable) = [...]Гетерогенныйvtable

Правила:

  1. Если правая часть — конкретный конструктор типа, известный в момент компиляции, генерируется IR с прямым вызовом.
  2. Если тип правой части не может быть определён в момент компиляции, выполняется откат на механизм vtable.
  3. vtable в качестве страховки гарантирует корректность полиморфизма в рантайме.

Поддержка утиной типизации ​

yaoxiang
// Присваивание интерфейсному типу возможно при наличии одноимённых методов
CustomPoint: Type = {
    draw: (self: CustomPoint, surface: Surface) -> Void,
    x: Float,
    y: Float
}

custom: CustomPoint = CustomPoint(
    (self: CustomPoint, surface: Surface) => surface.plot(self.x, self.y),
    1.0,
    2.0
)

Изменения синтаксиса ​

РаньшеПосле
type Point = Point(x: Float, y: Float)type Point = { x: Float, y: Float }
type Result(T, E) = ok(T) | err(E)Result: (T: Type, E: Type) -> Type = { ok: (T) -> Result(T, E), err: (E) -> Result(T, E) }
требуется ключевое слово implключевое слово не требуется, имена интерфейсов записываются в конце тела типа

Устаревший синтаксис: варианты с | ​

Объявление об устаревании (25.07.2026): синтаксис вариантов с | официально устарел и удалён из реализации.

Следующие записи больше не поддерживаются:

type Color = red | green | blue                # ❌ устарело
type Result(T, E) = ok(T) | err(E)             # ❌ устарело
type Option(T) = some(T) | none                # ❌ устарело

Суммы и типы выражаются единообразно через record type. Когда record type содержит только функции, каждая из которых возвращает сам этот тип, он является типом-суммой:

yaoxiang
Color: Type = {
    red: () -> Color,
    green: () -> Color,
    blue: () -> Color
}

Result: (T: Type, E: Type) -> Type = {
    ok: (T) -> Result(T, E),
    err: (E) -> Result(T, E)
}

Option: (T: Type) -> Type = {
    some: (T) -> Option(T),
    none: () -> Option(T)
}

Обоснование дизайна:

  1. Устранение особого случая: | — единственная синтаксическая форма в BNF, отличная от name: type = value. После удаления продукция type_expr полностью унифицирована, парсеру больше не нужно поддерживать отдельный путь и просмотр с возвратом для вариантных типов.
  2. Математическая эквивалентность: в рамках изоморфизма Карри–Ховарда дизъюнкция P ⊕ Q, соответствующая типу-сумме, эквивалентна record type, «поля которого — функции, возвращающие сам этот тип». Семантика та же, две формы синтаксиса не нужны.
  3. Нулевое разрушение: до удаления синтаксис | в парсере поддерживался наполовину (варианты без аргументов разбирались, но типы аргументов терялись при мономорфизации), и ни один пользовательский код на него не полагался.
  4. Упрощение AST: узел Type::Variant(Vec<VariantDef>) удаляется, все вариантные типы проходят через путь Type::Struct, специальные ветки в typecheck/mono/formatter полностью устранены.

Примечание: семантические свойства типа-суммы (конструирование варианта, проверка полноты match, размещение tagged union в памяти) выводятся на уровне typecheck из структуры Type::Struct и не зависят от отдельного узла AST. Полная семантика конструирования вариантов приведена в следующем разделе «Конструирование вариантов record-типа суммы (авторитетное определение)».

Конструирование вариантов record-типа суммы (авторитетное определение) ​

Данный раздел раскрывает критерии распознавания из раздела «Устаревший синтаксис: варианты с |» в полную семантику (утверждено 25.09.2026, issue #341, предварительное условие для RFC-011b, фаза 2). Четыре решения: вызов с квалификацией типа, форма вызова вариантов с нулевой нагрузкой, самодостаточный вывод типа, полное или полностью отсутствующее повышение имени варианта.

Правило распознавания: всё или ничего ​

Record type считается типом-суммой при одновременном выполнении двух условий:

  1. Все поля тела типа имеют функциональный тип;
  2. Возвращаемый тип каждого поля после подстановки параметров типа (Self/аргументы дженериков) равен самому этому record type.

Распознавание работает по принципу всё или ничего: при выполнении условий все функциональные поля одновременно повышаются до конструкторов вариантов; при невыполнении весь тип — обычный record (функциональные поля являются полями данных, хранящими значения функций), и смешанной формы «частично варианты, частично данные» не существует. Если требуется одновременно нести данные и вариантную семантику, моделируются два типа — record данных и тип-сумма, без слияния объявлений.

Форма вызова: с квалификацией типа, без голого имени ​

Единственная форма вызова конструктора вариантов — вызов с квалификацией типа: получение члена варианта на значении типа с последующим вызовом:

yaoxiang
r1 = Result(Int, String).ok(5)   // Result(Int, String), полезная нагрузка 5
c  = Color.red()                 // вариант с нулевой нагрузкой: вызов в форме функции, согласующейся с сигнатурой поля

Форма с голым именем не предоставляется (ok(5) не является вызовом конструктора). Причина: голое имя требует вывода принадлежности к типу-сумме и его аргументам типа из контекстного ожидаемого типа, тогда как в данном языке тип должен быть явно записываемым, вывод течёт однонаправленно из позиции ожидания (та же дисциплина, что и в RFC-011a, «Self — явный параметр типа, без магии»). В форме с квалификацией тип полностью самодостаточен и не требует контекста. Если в будущем голое имя и будет введено, оно может быть только «развёрткой-сахаром при однозначной определимости из контекста», а семантическим базисом останется определение с квалификацией из данного раздела.

Вывод: самодостаточность типа ​

При полном указании аргументов типа в квалифицированном имени сигнатура конструктора варианта есть результат подстановки аргументов типа в сигнатуру поля:

yaoxiang
Result(Int, String).ok   // : (Int) -> Result(Int, String)

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

Повышение полей: имена вариантов — не поля данных ​

После распознавания типа как типа-суммы имена вариантов удаляются из пространства полей данных — доступ к полю по имени варианта на значении типа-суммы отвергается на этапе компиляции:

yaoxiang
r1.ok    // ошибка компиляции: ok — конструктор варианта Result, а не поле данных

Причина: значение типа-суммы в рантайме есть tagged union (см. ниже) и не несёт таблицы объявления вариантов; разрешение доступа к полю неизбежно привело бы к тихой неверной интерпретации. Это — продолжение той же дисциплины, что и RFC-011a §1.2 (единое пространство имён полей/методов, конфликт — ошибка). Для конструирования используется квалификация типа (Result.ok), для использования — деструкция через match (RFC-010b).

Представление в рантайме и равенство ​

Представление значения типа-суммы в рантайме — tagged union: Enum { идентичность_типа, variant_id, payload }.

  • Идентичность типа несёт конкретный тип-сумму (не глобальный заполнитель) — значения разных типов-сумм несравнимы;
  • variant_id нумеруется по порядку объявления вариантов в теле типа; этот же порядок служит входными данными для проверки полноты в RFC-010b;
  • равенство (==/!=): идентичность типа совпадает, variant_id совпадает, payload поэлементно равен (рекурсивно).

Миграция std ​

std.result переходит на механизм данного раздела для определения ok / err (нативные result_ok / result_err отходят на второй план), конструирование вариантов в std идёт по тем же правилам, что и пользовательские типы-суммы, без привилегированных каналов. Демонтаж специальных parser-обработок Result / Option в позиции типа и специальных обработок make_result по имени осуществляется в фазе 2 RFC-011b — после интерфейсизации ?Result становится обычным типом-суммой из std.

Логические операторы: and / or / ! (авторитетное определение, в стиле Zig) ​

Объявление определения (03.08.2026): авторитетная форма логических операторов — ключевые слова and / or + символьный унарный ! (согласовано с таблицей приоритетов SPEC syntax.md §2.2). Данный дизайн выровнен с Zig: управление потоком с коротким замыканием — через ключевые слова, чистая унарная операция — через символ. Ранние реализации, дрейфовавшие к C с && / ||, и промежуточное ключевое слово not удалены.

Семантика:

ОператорПриоритет (SPEC §2.2)АссоциативностьСемантика
!3 (унарный префикс, плотное связывание)справа налевологическое отрицание (чистая функция, без потока управления)
and10слева направокороткое замыкание логического И
or10слева направокороткое замыкание логического ИЛИ
yaoxiang
# Короткое замыкание: при and слева false / при or слева true правая часть не выполняется
if x != 0 and y / x > 1 { ... }   # при x == 0 деления на ноль не будет

# Плотное связывание: !a == b ≡ (!a) == b (в стиле Zig; в отличие от Python not a == b ≡ not (a == b))
!3 == 4          # false: (!3) == 4 → false
!(3 == 4)        # true
!x != 0          # ≡ (!x) != 0
!list.is_empty(xs)   # ≡ !(list.is_empty(xs)), отрицание после вызова

Следующие записи больше не поддерживаются (лексер сообщает об ошибке и подсказывает соответствующую форму):

x && y     # ❌ удалено, используйте x and y
x || y     # ❌ удалено, используйте x or y
not x      # ❌ удалено, используйте !x (not снова становится обычным идентификатором; != не затрагивается)

Обоснование дизайна (выровнено с Zig, ziglang/zig#272 / #6625):

  1. Короткое замыкание — поток управления → ключевое слово; чистая функция — операция → символ. and / or изменяют порядок вычисления (правая часть по необходимости пропускается), что по природе аналогично if — отсюда ключевое слово; ! выполняет чистое отрицание уже вычисленного операнда, что по природе аналогично - + — отсюда символ. В YaoXiang распространение ошибки использует ? (§2.11), конфликта с ! нет.
  2. Плотное связывание устраняет неоднозначность: ! визуально «прилеплен» к операнду, высокий приоритет очевиден; ключевое слово not вынуждено отделяться пробелом от операнда, и его связывание (not a == b) вызывает мысленную неоднозначность.
  3. Разделение понятий: & несёт двойную нагрузку — маркер заимствования (&p / &mut p, RFC-009) и побитовое И (приоритет 8 в SPEC §2.2). Добавление && заставит один символ нести три значения. and / or / ! полностью визуально разделяют заимствование, побитовые операции и логику.
  4. Прецедент: Zig (современный системный язык той же ниши) использует именно комбинацию ключевых слов and / or + символ !; Python / Lua / Ada / SQL — полностью ключевые слова (включая not); семейство C — полностью символы — YaoXiang берёт комбинацию Zig, получая преимущества обоих подходов.
  5. Согласованность с Карри–Ховардом: тип как утверждение (см. раздел об изоморфизме выше), логические связки в уточняющих типах записываются как and / or (например, { 0 <= idx and idx < arr.len }) — естественная форма записи утверждений; ! как символ унарного отрицания соответствует ¬.

Реализация: and / or раскрываются на уровне IR в последовательность переходов с коротким замыканием (a and b ≡ if a { b } else { false }), ! разбирается как плотно связывающий унарный (операнд по BP_UNARY + 1). Регрессионные тесты: tests/yaoxiang/01-syntax/basics/logical_ops.yx, logical_not.yx.

Пояснение к дизайну синтаксиса: именованные функции — синтаксический сахар лямбды ​

Ключевое понимание ​

Именованная функция и лямбда-выражение — одно и то же! Единственное отличие: именованная функция даёт лямбде имя.

yaoxiang
// Эти две записи по сути идентичны
add: (a: Int, b: Int) -> Int = a + b           // именованная функция (рекомендуется)
add: (a: Int, b: Int) -> Int = (a, b) => a + b        // лямбда-форма (полностью эквивалентна)

Модель синтаксического сахара ​

// Именованная функция = лямбда + имя
name: (Params) -> ReturnType = body

// По сути это
name: (Params) -> ReturnType = (params) => body

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

Правила области видимости параметров ​

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

yaoxiang
x = 10  // внешняя переменная

double: (x: Int) -> Int = x * 2  // ✅ параметр x перекрывает внешний x, результат 20

Гибкость позиции аннотации ​

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

Позиция аннотацииФормаПримечание
Только в сигнатуреdouble: (x: Int) -> Int = x * 2✅ рекомендуется
Только в заголовке лямбдыdouble = (x: Int) => x * 2✅ допустимо
В обеих позицияхdouble: (x: Int) -> Int = (x) => x * 2✅ избыточно, но разрешено

Полный пример ​

yaoxiang
// ✅ Рекомендуется: сигнатура полная, заголовок лямбды опускается
add: (a: Int, b: Int) -> Int = a + b
inc: (x: Int) -> Int = x + 1
main: () -> Void = { print("hi") }

// ✅ Допустимо: типы аннотируются в заголовке лямбды
double = (x: Int) => x * 2

// ✅ Допустимо: аннотация в обеих позициях
double: (x: Int) -> Int = (x) => x * 2

Преимущества дизайна ​

СвойствоПреимущество
КраткостьПри полной сигнатуре не нужно повторять имена параметров
ГибкостьСохранена лямбда-форма — используйте ту, что нравится
ЕдинообразиеСовпадает с шаблоном объявления переменной x: Int = 42
Интуитивностьname: Type = body напрямую соответствует «имя name, тип Type, значение body»

Компромиссы ​

Преимущества ​

ПреимуществоОписание
Предельное единствоОдно правило синтаксиса покрывает все случаи
Теоретическая элегантностьИдеально симметричное name: type = value
Без новых ключевых словПовторное использование существующих элементов
Простота реализацииКомпилятор обрабатывает лишь одну форму объявления
Простота обученияЗапомнив один шаблон, можно писать любой код
Лёгкость расширенияНовые средства естественно вписываются в модель

Недостатки ​

НедостатокОписание
Соглашение об именованииМетоды должны следовать именованию Type.method
МногословностьПолный синтаксис длиннее сокращённого, но допускает вывод
Кривая обученияТребуется понимание единой модели

Меры по смягчению ​

yaoxiang
// 1. Понятные сообщения об ошибках
// Пример ошибки компиляции:
// Error: Point does not implement Serializable
//   Required method 'serialize: (self: Point) -> String' not found
//   Note: Define Point.serialize to implement Serializable

// 2. Вывод типов
// Тип можно опустить, компилятор выполнит вывод
Point.draw = (self: Point, surface: Surface) => surface.plot(self.x, self.y)

// 3. Подсказки IDE
// IDE автоматически подсказывает недостающие методы

Риски ​

РискВоздействиеМера по смягчению
Сложность разбораЕдиный синтаксис может усложнить разборИспользование рекурсивного нисходящего парсера
Накладные расходы производительностиПоиск через vtable может дать накладные расходыМономорфизация на этапе компиляции

Пасхалка 🎮: Исток языка ​

✨ Type: Type = Type ✨

yaoxiang
// Попробуем определить тип типа...
Type: Type = Type

Предупреждение: это — неименуемая сущность!

╔══════════════════════════════════════════════════════════════╗
║                                                              ║
║   Одно порождает два, два порождает три, три порождает тьму вещей.  ║
║   Предел порождает пределы.                                          ║
║                                                              ║
║   Type: Type = Type                                          ║
║   Это — исток YaoXiang, граница языка.                          ║
║   Здесь компилятор безмолвствует, философия останавливается.          ║
║                                                              ║
║   Спасибо, что достигли философской границы языка.                  ║
║                                                              ║
╚══════════════════════════════════════════════════════════════╝

Примечание: компилятор не может корректно обработать Type: Type = Type (это приведёт к парадоксу вселенных Type0/Type1), но мы намеренно сохранили этот «пасхальный яйцо» — при попытке его скомпилировать вы получите дзен-сообщение от создателя языка. Это не просто техническая граница, а дань уважения YaoXiang философии типов.


Приложение ​

БНФ синтаксиса ​

bnf
program ::= statement*

statement ::= declaration | expression

# Единое объявление: name: Type = expression
declaration ::= identifier ':' type_expr '=' expression

# Выражение типа
type_expr ::= identifier
       | identifier '(' type_expr (',' type_expr)* ')'      # применение типа
       | '(' type_expr (',' type_expr)* ')' '->' type_expr       # функциональный тип
       | '{' type_field* '}'                       # record / интерфейс
       | 'Type'                                    # мета-тип

type_field ::= identifier ':' type_expr
             | identifier                           # ограничение интерфейса

# Параметры дженериков: как часть функционального типа, напр. (T: Type, R: Type) -> (...)
# Отдельное правило БНФ не требуется — параметры : Type — это обычные параметры функции

# Выражение
expression ::= literal
              | identifier
              | identifier '(' expression (',' expression)* ')'  # вызов функции / вызов конструктора
              | '(' expression (',' expression)* ')'              # кортеж
              | expression '.' identifier '(' arguments? ')'    # вызов метода
              | lambda
              | '{' field ':' expression (',' field ':' expression)* '}'

arguments ::= expression (',' expression)*

lambda ::= '(' parameter_list? ')' '=>' block

block ::= expression | '{' expression* '}'

Глоссарий ​

ТерминОпределение
ОбъявлениеОператор присваивания в форме name: type = value
Record typeТип { ... } с именованными полями
ИнтерфейсRecord type, все поля которого — функциональные типы
Родовой типТип, определённый как Name: (T: Type) -> Type = { ... }, принимающий параметры типа
Функция в пространстве имёнФункция вида Type.name, принадлежащая пространству имён Type. Не предполагает никакой привязки
Привязка методаType.name = func[n], привязка позиции n функции func как вызывающей стороны, делает доступным синтаксис obj.name(args)
Родовая функцияФункция с синтаксисом (T: Type), параметры типа — первая группа параметров
Мета-типType, единственный маркер уровня типа в языке

Жизненный цикл и судьба ​

┌─────────────┐
│   Черновик  │  ← текущее состояние
└──────┬──────┘
       │
       ▼
┌─────────────┐
│  На рассмотрении │  ← открыто для обсуждения сообществом и обратной связи
└──────┬──────┘
       │
       ├──────────────────┐
       ▼                  ▼
┌─────────────┐    ┌─────────────┐
│  Принято    │    │  Отклонено  │
└──────┬──────┘    └──────┬──────┘
       │                  │
       ▼                  ▼
┌─────────────┐    ┌─────────────┐
│   accepted/ │    │    rfc/     │
│ (официальный дизайн) │    │ (остаётся на месте) │
└─────────────┘    └─────────────┘