Axiom Справочник языка

12 · Пользовательские типы

Оператор TYPE

TYPE — оператор языка Axiom, который объявляет пользовательский тип записи с именованными полями. Тип может содержать методы, перегруженные конструкторы и проверку IsValid после конструктора.

Назначение и контекст

TYPE объединяет связанные данные, например имя и возраст человека, в один пользовательский тип. Каждый экземпляр хранит собственные поля записи. Методы задают действия над экземпляром, а конструктор выполняет его явную начальную настройку. TYPE объявляется на верхнем уровне программы и доступен в DIM, параметрах и результатах функций. SCREEN не требуется.

Синтаксис объявления

TYPE <имя типа>
    <имя поля> AS <тип поля>
END TYPE
TYPE <имя типа>
    <имя поля> AS <тип поля> = <значение по умолчанию>
END TYPE
TYPE <имя типа>
    <имя массива>[] AS <тип элемента>
    <имя фиксированного массива>[<границы>] AS <тип элемента>
END TYPE

Внутри TYPE также можно объявить SUB и FUNCTION с обычным синтаксисом подпрограмм. Конструктор записывается как SUB New, а проверка после конструктора — как FUNCTION IsValid() AS BOOLEAN.

Строгая запись временных значений

Строгая временная запись допускает дробную часть секунд только после точки с одной–семью цифрами. Точка без цифр, восемь цифр долей, NUL и невозможный календарный день отклоняются. Для UTCDATETIME признак Z ставится после секунд или их дробной части: например, 2024-02-29T12:34:56.1234567Z; запись 2024-02-29T12:34:56.Z недопустима.

Неверный канонический текст начального значения временного поля TYPE отклоняется при проверке объявления с MB2027. Допустимые дробные секунды сохраняются полностью; они не округляются до целых секунд.

Части объявления

Имя типа становится названием пользовательского типа данных. Имя поля выбирает значение внутри записи. Тип поля задаёт допустимые значения: числовой, строковый, логический или временной тип, другой TYPE либо массив допустимых элементов. Поля FILE и FUNCTION не поддерживаются.

Значение по умолчанию задаёт начальное скалярное или строковое поле. Оно записывается константным литералом, который либо уже имеет тип поля, либо допускает предусмотренное языком числовое преобразование к этому типу. Поэтому поле DOUBLE принимает 1, а поля LONG и DECIMAL принимают целый литерал; преобразование выполняется при объявлении типа, и поле хранит значение уже целевого типа. Выражения с вызовами, переменными, полями, массивами и арифметическими операциями здесь недопустимы. Поле DATE, TIME или DATETIME принимает строковый литерал в канонической записи: yyyy-MM-dd, HH:mm:ss или yyyy-MM-dd HH:mm:ss, для TIME и DATETIME допускается дробная часть секунд. Значение проверяется при объявлении и хранится уже целевым типом, поэтому невозможная календарная дата отклоняется с MB2027, а полный текст даты-времени в поле DATE или TIME — с MB2003 по общему правилу потери компонента. Вызовы функций, включая DATETIMEVALUE и DATESERIAL, в значении по умолчанию недопустимы. Обычная строка изначально пуста, число равно 0, BOOLEAN равен FALSE; заданный литерал заменяет обычное начальное значение.

Имя массива задаёт массивное поле. Пустые квадратные скобки определяют динамическое поле без элементов; [] задаёт одно измерение, [,] — два. Границы фиксированного поля должны быть целочисленными константными выражениями, при необходимости использующими глобальные целочисленные CONST. Тип элемента общий для всех координат. STRING * N разрешён для строкового поля и строковых элементов фиксированной длины.

Чтение и изменение поля

<запись>.<поле>
<запись>.<поле> = <значение>

Запись — переменная или выражение пользовательского типа. Поле обозначает объявленное имя, а значение присваивания должно быть совместимо с типом поля. Вложенный доступ последовательно выбирает записи: user.Home.City. Для массива записей сначала выбирается элемент: people[1].Name.

Создание записи

DIM <переменная> AS <имя типа>
<имя типа>()
<имя типа>(<поле> := <значение>⟦, ...⟧)
<имя типа>(<позиционные значения>)

DIM создаёт поля со значениями по умолчанию и не вызывает конструктор. Если конструкторов нет, форма без аргументов делает такую же запись. Именованная форма задаёт поля в любом порядке; пропущенные поля сохраняют начальные значения. Она не вызывает конструктор и IsValid.

Если конструкторов нет, позиционная форма задаёт все поля в порядке объявления и требует ровно столько значений, сколько полей. Если объявлен хотя бы один конструктор, неименованная форма выбирает подходящий SUB New по числу и типам аргументов; обычная форма без аргументов также требует подходящий New(). Смешивать позиционные и именованные значения нельзя.

Пример именованной и позиционной формы

TYPE Person
    Name AS STRING = "Unknown"
    Age AS INTEGER = 0
END TYPE
DIM user AS Person
user = Person(Name := "Ada")
PRINT user.Name
PRINT user.Age
user = Person("Grace", 40)
PRINT user.Name
PRINT user.Age

Ожидаемый вывод:

Ada
0
Grace
40

Массивные и вложенные поля

Динамическое массивное поле выделяется оператором REDIM и может освобождаться через ERASE. Фиксированное массивное поле сразу получает элементы и запрещает REDIM и ERASE. Ранг и тип элементов сохраняются; поля могут быть многомерными. Прямая и косвенная циклическая вложенность TYPE запрещена.

REDIM <запись>.<массивное поле>[<границы>]
<запись>.<массивное поле>[<индексы>] = <значение>

Границы и индексы имеют те же числовые правила, что у обычного массива. Запись сначала выбирает экземпляр TYPE, массивное поле — его хранилище, а индексы — элемент. Массив целиком обязательно записывается с пустым суффиксом [], например bag.fruits[], независимо от ранга поля. Поэтому присваивание имеет вид bag.fruits[] = SPLIT("яблоко,банан", ","), а освобождение динамического поля — ERASE bag.fruits[]. Фиксированное поле сохраняет запрет ERASE и REDIM.

Методы и SELF

Метод SUB или FUNCTION объявляется внутри TYPE и вызывается через точку у конкретной записи. SELF обозначает эту запись и вводится автоматически; объявлять SELF вручную не нужно. Поля внутри метода читаются и изменяются через SELF.ИмяПоля. Самостоятельное имя поля без SELF не заменяет такой доступ.

<запись>.<метод>(⟦<аргументы>⟧)

Запись задаёт экземпляр, метод — объявленную подпрограмму этого TYPE, аргументы передаются по обычным правилам BYVAL и BYREF. Методы можно перегружать по числу и типам параметров. Процедура не возвращает значения, функция возвращает результат объявленного типа.

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

TYPE Counter
    Value AS INTEGER = 0
    SUB Add(amount AS INTEGER)
        SELF.Value = SELF.Value + amount
    END SUB
    FUNCTION Current() AS INTEGER
        RETURN SELF.Value
    END FUNCTION
END TYPE
DIM counter AS Counter
counter.Add(3)
PRINT counter.Current()

Ожидаемый вывод:

3

Конструктор SUB New

TYPE <имя типа>
    <поля>
    SUB New(⟦<параметры>⟧)
        <инструкции>
    END SUB
END TYPE

Поля задают структуру записи; параметры задают входные аргументы конструктора по обычному договору SUB. Перед входом в New поля уже имеют начальные значения. Инструкции конструктора могут менять поля через SELF. Поддержаны несколько перегрузок New с разными сигнатурами; наиболее точное совпадение типов выбирается до допустимых преобразований.

Проверка IsValid

FUNCTION IsValid() AS BOOLEAN
    RETURN <условие>
END FUNCTION

Метод IsValid не принимает видимых параметров и возвращает BOOLEAN. Условие проверяет текущие поля через SELF. После успешного конструктора проверка вызывается автоматически; FALSE вызывает MB3005 и отклоняет создание. Именованный литерал, DIM и позиционный литерал без конструктора этой проверки не вызывают.

TYPE Person
    Name AS STRING = "Unknown"
    Age AS INTEGER = 0
    SUB New(name AS STRING, age AS INTEGER)
        SELF.Name = name
        SELF.Age = age
    END SUB
    FUNCTION IsValid() AS BOOLEAN
        RETURN SELF.Name <> "" AND SELF.Age >= 0
    END FUNCTION
END TYPE
DIM user AS Person
user = Person("Ada", 36)
PRINT user.Name
TRY
    user = Person("", -1)
CATCH err
    PRINT err.Code
END TRY
user = Person(Name := "", Age := -1)
PRINT user.Age

Ожидаемый вывод:

Ada
MB3005
-1

Копирование и сравнение

Присваивание TYPE создаёт независимую копию всех данных записи: скалярных полей, вложенных записей, фиксированных и динамических массивов, словарей и множеств. Изменение вложенных данных копии не меняет оригинал. Строки фиксированной длины сохраняют длину и нормализацию. BYVAL использует то же правило, а BYREF и получатель SELF явно адресуют исходную запись.

Операторы = и <> сравнивают записи одного пользовательского типа по полям, если все поля поддерживают структурное сравнение. Наличие массивного поля запрещает такое сравнение; упорядочивание записей не поддерживается.

Скалярные поля DATE, TIME, DATETIME и UTCDATETIME поддерживают такое сравнение, в том числе внутри вложенных записей. Поля UTCDATETIME сравниваются по моментам с полной точностью: различие в одной десятой миллионной секунды делает записи неравными.

TYPE Stamp
    Moment AS UTCDATETIME
END TYPE
DIM first AS Stamp
DIM second AS Stamp
first.Moment = "2024-02-29T12:34:56.0000001Z"
second.Moment = "2024-02-29T12:34:56.0000001Z"
PRINT first = second
second.Moment = "2024-02-29T12:34:56.0000002Z"
PRINT first <> second

Ожидаемый вывод: TRUE, затем TRUE.

TYPE Address
    City AS STRING
END TYPE
TYPE Person
    Name AS STRING
    Home AS Address
END TYPE
DIM first AS Person
DIM second AS Person
first.Name = "Ada"
first.Home.City = "London"
second = first
first.Name = "Grace"
first.Home.City = "Paris"
PRINT second.Name
PRINT second.Home.City

Ожидаемый вывод:

Ada
London
TYPE Collection
    Shared[] AS INTEGER
    Owned[1 TO 2] AS INTEGER
END TYPE
DIM first AS Collection
DIM second AS Collection
REDIM first.Shared[1 TO 2]
first.Shared[1] = 3
first.Owned[1] = 4
second = first
first.Shared[1] = 8
first.Owned[1] = 9
PRINT second.Shared[1]
PRINT second.Owned[1]

Ожидаемый вывод:

8
4

Правила доступа и создания

Оператор TYPE объявляет структуру; DIM создаёт переменную этой структуры. Поля доступны через точку, массивные поля — через точку и индексы. Для настройки с проверкой используйте конструктор; для прямого заполнения выбранных полей — именованный литерал. Динамические массивы внутри копии записи независимы от массивов оригинала.

Временные значения по умолчанию

Поле DATE, TIME или DATETIME принимает строковый литерал в канонической записи. Значение проверяется при объявлении типа и хранится уже целевым типом, поэтому запись создаётся с готовым временным значением без повторного разбора.

TYPE Event
    Day AS DATE = "2024-02-29"
    Clock AS TIME = "12:34:56.125"
    Moment AS DATETIME = "2024-02-29 12:34:56.125"
END TYPE
DIM item AS Event
PRINT item.Day
PRINT item.Moment

Ожидаемый вывод:

2024-02-29
2024-02-29 12:34:56.125

Невозможная календарная дата отклоняется с MB2027. Полный текст даты-времени в поле DATE или TIME отклоняется с MB2003 по общему правилу потери компонента: для сужения используют DATEVALUE или TIMEVALUE в самой программе, а не в объявлении типа. Вызовы функций, включая DATESERIAL и DATETIMEVALUE, в значении по умолчанию недопустимы, потому что поле принимает только константный литерал.

Диагностика

MB2002 означает повторное имя типа, поля или сигнатуры метода. MB2021 означает неизвестный тип после AS. MB2022 возникает для недопустимого поля, циклической структуры или неподдерживаемого члена TYPE. MB2023 означает неизвестное поле, MB2024 — доступ к полю значения неподходящего типа.

MB2025 запрещает REDIM и ERASE для фиксированного массивного поля. MB2027 сообщает о недопустимом значении по умолчанию. MB2031 означает смешивание именованных и позиционных значений, MB2032 — неверное число позиционных значений литерала. MB2004 означает неверное число аргументов конструктора.

MB2033 означает неоднозначный выбор перегрузки конструктора или метода. MB2044 требует SELF для обращения к полю в методе. MB3005 возникает, если IsValid вернул FALSE после конструктора; ошибки внутри конструктора передаются с собственными кодами.

MB3013 возникает, когда фиксированное массивное поле нельзя выделить. Запись создаётся в момент исполнения инструкции объявления или создания, поэтому превышение предела массива обнаруживается только при фактическом создании записи и перехватывается обычным TRY. Объявление процедуры с таким полем или объявление TYPE без создания значения отказа не вызывает.

См. также

SUB · FUNCTION · Вызовы · Массивы · TRY

Доступность в модуле

Перед объявлением верхнего уровня можно указать PUBLIC или PRIVATE. Без модификатора имя закрыто внутри файла. Модификатор PUBLIC открывает имя для файлов, которые явно импортируют текущий модуль. Он не меняет остальные правила объявления.

PUBLIC TYPE name
PRIVATE TYPE name

Одноимённые закрытые TYPE разных файлов независимы: совпадение имени не объединяет их поля и не делает записи совместимыми. Открытая функция может принимать или возвращать запись закрытого типа. Полученная запись сохраняет обычные правила доступа к полям; PRIVATE ограничивает имя типа, а не поля записи и не запрещает их изменение само по себе. Пример независимых закрытых типов.

Вложенные объявления не получают модификатора. Подробнее: PUBLIC и PRIVATE и IMPORT.

Кортежи

Связанные значения разных типов можно хранить в кортеже, объявлять с полным или общим типом группы, передавать функции и распаковывать. Все формы и примеры приведены на странице кортежей.