Данные и управление

Записи и номинальные данные

Как выбрать структурную запись, record, enum, объединение или newtype для данных предметной области.

Эта страница описывает Топаз v5.17. Прежде чем считать её актуальной, сверьте поле version в /version.json со значением 5.17.

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

Запустите пример

Сохраните код в файле records-nominal-data.tpz:

TOPAZ
type ОтображаемоеЗначение = int | string

newtype ИдентификаторПользователя = int

enum Состояние {
    Активен,
    Приостановлен(string),
}

record Пользователь {
    идентификатор: ИдентификаторПользователя,
    имя: string,
    состояние: Состояние = Состояние.Активен,
}

function меткаСостояния(состояние: Состояние) -> string {
    match состояние {
        case Активен => "active"
        case Приостановлен(причина) => причина
    }
}

function показать(значение: ОтображаемоеЗначение) -> string {
    match значение {
        case число: int => "number {число}"
        case текст: string => текст
    }
}

let до: Пользователь = Пользователь {
    идентификатор: ИдентификаторПользователя(7),
    имя: "Ada",
}
let после: Пользователь = Пользователь {
    ...до,
    состояние: Состояние.Приостановлен("review"),
}
let сводка = { имя: "Ada", возраст: 36 }
let старше = сводка{ возраст: 37 }
let готово = показать("ready")

print("{до.идентификатор.value()}:{до.имя}:{меткаСостояния(до.состояние)}")
print("{после.имя}:{меткаСостояния(после.состояние)}")
print("{сводка.возраст}:{старше.возраст}")
print("{показать(7)}:{готово}")
BASH
topaz check records-nominal-data.tpz
topaz run records-nominal-data.tpz

Программа напечатает:

Вывод
7:Ada:active
Ada:review
36:37
number 7:ready

Выберите форму данных

ФормаКогда выбиратьСоздание и чтение
Структурная записьЛокальному значению достаточно известного набора полей{ name: "Ada", age: 36 }, затем value.field
Номинальная recordВажны имя предметной области и принадлежность объявлениюПользователь { ... }; поля, значения по умолчанию, именное обновление и шаблоны
enumЗначение является ровно одним вариантом из закрытого набораСостояние.Активен или Состояние.Приостановлен(причина); выбор через match
Объединение A | BПодходит значение одного из уже существующих типовСужение с помощью литерального, конструкторного или типового шаблона
newtypeБазовому значению нужна отдельная предметная идентичностьИдентификаторПользователя(7), затем снятие одного слоя методом .value()

Пользователь — номинальная запись. Если поле состояние пропущено, его значение по умолчанию вычисляется один раз, поэтому до получает состояние Активен. Форма Пользователь { ...до, состояние: ... } создаёт новую внешнюю запись, сохраняет остальные поля, не меняет до и не запускает значения по умолчанию повторно.

Состояние — закрытое перечисление. При создании используется пространство имён (Состояние.Приостановлен), а в шаблоне — короткое имя варианта (case Приостановлен(причина)). Благодаря этому проверяющий может убедиться, что меткаСостояния обрабатывает все варианты.

ОтображаемоеЗначение — псевдоним объединения, а не новая оболочка времени выполнения. Типовые шаблоны в показать сужают существующее значение до int или string и связывают его с именем. ИдентификаторПользователя, напротив, остаётся отдельной номинальной оболочкой, хотя его базовый тип — int.

сводка — структурная запись: её совместимость определяется формой полей. Она обновляется как сводка{ возраст: 37 }. Это другая операция, не номинальное обновление Пользователь.

Создание, обновление и разбор

Поля номинальной записи проверяются по имени и типу. Явно заданные поля вычисляются слева направо, после них пропущенные значения по умолчанию вычисляются в порядке объявления. Номинальное обновление допускает один начальный ...source, за которым идут заменяемые поля. Копируется только внешняя запись, поэтому изменяемые значения внутри полей остаются общими.

Шаблон case Пользователь { имя } => сначала проверяет, что значение создано объявлением Пользователь, а затем читает выбранные поля. Неуказанные поля игнорируются, но хотя бы одно поле назвать необходимо. Структурная запись с такими же полями всё равно не становится Пользователь.

Частая ошибка

Форма JavaScript { ...value } не является обновлением структурной записи в Топазе. Для структурной записи используйте value{ field: replacement }, а для номинальной — Name { ...value, field: replacement }.

Не помещайте в тело записи синтаксис получателя из Rust. Неявных this, &self и &mut self нет. Если нужен метод-получатель, ограниченная форма Топаза записывается в отдельном блоке impl Name верхнего уровня и принимает обычный неизменяемый self по значению.

Точные границы

  • Записи, перечисления и новые типы объявляются на верхнем уровне модуля и имеют номинальную идентичность.
  • Параметры обобщённых номинальных типов инвариантны; при создании им нужен ожидаемый тип или другой разрешённый контекст вывода.
  • Сравнение, порядок и ключи коллекций для номинальных значений сохраняют не только содержимое, но и принадлежность объявлению.
  • Шаблоны перечислений используют короткие имена вариантов; полнота проверяется по ветвям без дополнительных условий.
  • В начале шаблона номинальной записи стоит простое имя без пространства имён, а пустой номинальный шаблон недопустим.
  • Конструктор newtype принимает ровно одно базовое значение, а .value() снимает ровно один слой.

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

Начните с урока Данные и управление. Псевдонимы и объединения описаны в разделе Типы, а полный разбор значений и связанные имена — в разделе Шаблоны и управление потоком.