Создание объектов
Область видимости объекта != время жизни объекта
У объекта имя есть всегда, даже тогда, когда сам объект физически еще не создан. Область видимости имени определяется его расположением в программном коде (блок кода, пространство имен, модуль и т.д.). Объект может существовать, но быть не доступен из текущей области видимости, например, перекрыт другим объектом с таким же именем.
Время жизни локального/автоматического (т.е. временного) объекта всегда ограничено областью видимости текущего блока кода, а время жизни статического объекта ограничено временем жизни модуля, в котором он определен или временем жизни программы, если объект глобальный. Но инициализация начального занчения статического объекта происходит в соответствии с выполняемым программным кодом, (т.е. при выполнении кода с оператором создания статического объекта).
Создания объектов
Для создания объектов в TrustLang используется несколько операторов:
-
“:=” — создаёт или присваивает значение переменной/функции (объекту по значению). Если объекта с таким именем ещё нет, он создаётся; иначе значение переприсваивается. Пример:
x := 5;,hello(str) := { ... };. -
“::=” — объявление типа (алиаса или типизированного типа), например
MyInt ::= :Int32;,Status ::= (OK=0, ERROR=1, BUSY=2,):Enum;. Применять “::=” для создания значений/функций нельзя — компилятор сообщает: “cannot assign a type … to a value variable; use ‘::=’ to declare a type alias”.
⚠️ НЕ РЕАЛИЗОВАНО: операторы `::-` и `:-` (чистые функции и значения, вычисляемые на этапе компиляции) в текущей версии НЕ реализованы
Операторы “::-” и “:-” в текущей версии не реализованы; ранее они предназначались для чистых функций и значений, вычисляемых на этапе компиляции.
Присвоение нового значения
Оператор “=” применяется для присвоения нового значения уже существующим объектам.
Если объект с указанным именем отсутствует, то возникает ошибка компиляции.
Если переопределяется функция или метод класса,
то старое значение не удаляется, а создается стек переопределенных имен (своего рода таблицу вирутальных имен времени компиляции),
и к старому (предыдущему) объекту можно обратиться из новой функции по системному имени “$$”.*
Оператор обмена значениями
Оператор “:=:” (swap) не создает новых переменных, а только обменивает их значения между собой.
Переменные должны иметь одинаковые/совместимые типы данных, либо иметь тип :None, т.е. "_".
Используется для реализации идиомы копирования и замены (copy-and-swap idiom), так как при выполнении оператора не может быть ошибок.
Оператор добавления элемента
Оператор “[]=” — аналог push_back: добавляет новый элемент к контейнеру слева, увеличивая его размер на единицу.
Общее правило: X []= v равносильно X.push_back(v).
- Для словаря (
Dict, в т.ч. позиционные «массивы») безымянный append добавляет позиционный элемент:d []= 999. - Для строк добавляет символ/строку:
s []= 'd'→s.append("d"). - Правый объект оператора должен быть совместим с типом единичного элемента левого объекта
(ширина строки: узкая
'...'/ широкая"..."должны совпадать с типом строкового контейнера). - Вложенный LHS (
d['x'] []= v,d[0] []= v,d.field []= v) — пока не реализован (ошибка компиляции). - Слияние словаря через оператор распаковки
...:d []= ... dict2илиd []= ... (a=1, b=2,)добавляет в цель все элементы словаря-операнда (аналогextend/update); размер цели растёт на число элементов (для литерала и словаря с известным размером — статически контролируется).
d := (1, two=2,);
d []= 999; # добавить позиционный элемент → size 3
d []= 'x'; # добавить ещё один элемент
d []= ... (a=100, b=200,); # слияние литерала → size 5 (добавлены a и b)
d2 := (three=3, 4,);
d []= ... d2; # слияние переменной-словаря → size 7
s := 'abc';
s []= 'd'; # строка стала "abcd"
@print(s); # > abcd
Добавление именованного элемента словаря выполняется оператором присваивания d[name] = value (установка/добавление по ключу).
Например, при определении класса :NewClass2:
:NewClass := :Class() { # Базовый класс
@::static := 0; # Однократная инициализация статического объекта класса
filed1 := 1; # Создание поля класса
filed2 := 2; # Создание поля класса
method1() := {}; # Регистрация метода
method2() := {}; # Регистрация метода
};
:NewClass2 := :NewClass() {
filed1 := 2; # Будет ошибка, т.к. field1 уже есть в базовом классе
filed2 := 2; # Используется поле field2 базового класса (или создается новое)
method1() := {}; # Будет ошибка, т.к. method1 уже есть в базовом классе
method1() = {}; # Аналог override, т.к. method должен существовать в базовом классе
};
$var := 99; # Создать переменную var
{
$var := 100; # Новая переменная перекрывает var более высокого уровня
@print('%d', $var) # > 100
}
@print('%d', $var) # > 99
Присваивание значения сразу нескольким переменным и оператор распаковки словаря
TrustLang поддерживает операцию присваивания значения сразу нескольким переменным, которые должны быть перечислены через запятую слева от оператора присвоения.
С правой стороны от оператора присвоения может находится только одно значение,
а для обмена занчениями двух переменных, вместо записи a,b = b,a;
нужно использовать оператор обмена:
a :=: b;
В качестве правого операнда в операторе присвоения допускается использовать оператор распаковки словаря … (многоточие), который можно использовать и при передаче аргументов в функцию.
args := (arg1=1, arg2=2, 3, 4,);
call(arg=0, ... args);
# Что равносильно вызову
call(arg=0, arg1=1, arg2=2, 3, 4);
Словарь может быть указан и с левой стороны от оператора присвоения — это деструктуризация.
Без маркера — точная привязка: каждая цель получает ровно один элемент, число целей должно
совпадать с числом элементов (для статически-известного размера). Суффикс ... у имени цели
(rest..., C++-pack) связывает оставшиеся элементы; _... — извлечь элементы, остаток отбросить;
одиночный _ — пропустить один элемент. Каждая цель типизируется runtime-типом своего элемента
(Int → Integer, Float → Double, Bool, StrChar…); внутри цикла тип расширяется до максимального
среди элементов (Bool/Int8 → Integer, float → Double), а для гетерогенного словаря используется
runtime-конвертер — арифметика над элементом работает как над Integer/Double.
Присваивание в существующие переменные — оператор = (a, b = ... d; или a, b = t;): цели
должны быть объявлены ранее (:=), новые переменные не создаются; иначе — ошибка компиляции.
Присваивание в константную цель (^) — ошибка. Цель может иметь явную аннотацию типа
(a:Int32, b := ... d; / a:Int32, b = t;) — она фиксирует тип объявляемой переменной: для кортежа
вместо auto генерируется int32_t c_a; для словаря переменная объявляется int32_t, а элемент
извлекается по runtime-типу хранения (std::any_cast<int64_t>(...), т.к. Dict нормализует целые в
int64_t). Без аннотации тип выводится.
Вложенная деструктуризация (a, (b, c) := t;) не поддерживается — только отдельные переменные.
Также не поддерживаются: деструктуризация по именованным ключам ({a: x, b: y} = d — раскрытие
только позиционное, по порядку вставки), «rest в середине» (a, rest..., c := seq — маркер
rest... допустим только последним) и аннотация типа на rest-цели (rest:MyDict... /
rest:MyTuple... — тип остатка всегда выводится: для словаря — Dict, для кортежа — под-кортеж;
аннотация → ошибка компиляции). Для кортежа rest-цель не может переиспользовать имя уже
существующей переменной (в т.ч. сам источник — a, t... := t): в отличие от spread-словаря, где
item, dict... := ... dict — легитимная идиома мутации источника, кортежный rest не мутация и
такое переиспользование — ошибка компиляции. При runtime-недостатке элементов (источник с
неизвестным на этапе сборки размером) выполнение завершается контролируемым abort с диагностикой —
никакого тихого дефолта (None/нуля) для любых типов целей (включая Any). Если тип элемента
словаря не выводится (например, словарь-параметр), цель вне цикла типизируется std::any с
предупреждением (симметрично цикловому widening) — не молча.
Пример цикла foreach для суммирования всех элементов словаря (или одномерного тензора) с использованием оператора раскрытия словаря (списка):
summa := 0;
dictionary := (1,2,3,4,5,);
@while( dictionary ) {
# Первый элемент словаря перемещается в item; суффикс `dictionary...` связывает
# оставшиеся элементы, мутируя источник (цикл завершается, когда словарь опустеет)
item, dictionary... := ... dictionary;
summa += item;
};
Заполнение размерных тензороов
⚠️ НЕ РЕАЛИЗОВАНО: описанные здесь способы заполнения размерных тензоров (`:Tensor[10](...)`, `...`-повторение, заполнение функцией/диапазоном) не реализованы — многомерные тензоры в текущей версии не реализованы
При заполнении данными размерных тензоров существует краткий способ указания их начальных значений. Для этого используется многоточие в следующих вариантах:
-
:Tensor[10](2,3, ...)- повторение всех предыдущих данных до полного заполнения тензора, что равносильно записи:Tensor[10](2,3, 2,3, 2,3, 2,3, 2,3). -
:Tensor[10](2, 3, ... 42 ...)- заполнение значением42до конца тензора, что равносильно записи:Tensor[10](2,3, 42,42,42,42,42,42,42,42). -
:Tensor[10](2, 3, ... rand() ...)- заполнение до конца тензора значением, которое будет возвращать функция. Указанная функция будет вызываться для каждого элемента до полного заполнения тензора (8 раз). -
:Tensor[10]( rand(), rand(), ...)- заполнение тензора двумя произвольными заначениями, то есть функцияrand()будет вызвана дважды только для двух первых элементов, после чего эти значения будут использоваться до полного заполнения тензора (как в первом примере). -
:Tensor[10]( ... 0..0.99..0.1 )- заполнение тензора элементами диапазона, что равносильно записи
:Tensor[10](0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9). Раскрытие диапазона создает фиксированное количество значений. Поэтому в данном примере размерность тензора можно не указывать, так как она будет создана автоматически. -
:Tensor[15]( ... 0..5 , ... )- заполнение тензора элементами диапазона и их повторение до заданного размера тензора::Tensor(0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4)
Заполнение данными в словарях и аргументах функций
Предыдущие способы заполнения данными возможны только для тензоров фиксированного размера, так как позволяют получить произвольное количество значений, однако их можно использовать только в конце списка значения для инициализации.
Операторы раскрытия словаря или диапазона создают ограниченное количество значений, но за счет этого их можно использовать не только для инициализации размерных тензоров, но и как начальные значений у словарей или аргументы при вызове функций, причем в произвольном месте, а не только в конце списка инициализации:
-
:Tensor( ... 0..0.99..0.1 )- заполнение тензора без указания его размера элементами из диапазона будет равносильно записи:Tensor(0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9) -
funtcion( 0, ... dict, 42 )- раскрытие словаря как индивидуальных не именованных аргументов функции, к примеру funtcion( 0, 1, 2, 3, 42), если словарьdictсодержал три элемента1,2и3. -
funtcion( ... ... dict, end=4)- раскрытие словаря как индивидуальных именованных аргументов функции. Если словарь dict будет содержать элементы(1, two=2, three=3,), то это будет равносильно вызову функцииfuntcion( 1, two=2, three=3, end=4). -
funtcion( ... ... dict, end=4)- раскрытие словаря как индивидуальных именованных аргументов функции. Если словарь dict будет содержать элементы(1, two=2, three=3,), то это будет равносильно вызову функцииfuntcion( 1, two=2, three=3, end=4). -
funtcion( ... :Tensor[5](... rand() ...) )- раскрытие тензора с пятью случайными значениями в качестве аргументов при вызове функции.