Создание объектов

Область видимости объекта =/= время жизни объекта

Область видимости объекта определяется его расположением в программном коде (блоки кода, пространство имен, модели и т.д.) Объект может существовать, но может быть не доступен из текущей области видимости.

Имя объекта, создание объекта и его расположение

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

Создания объектов и присвоения новых значений

Для создания объектов и присвоения им новых значений в TrustLang используется несколько операторов:

  • ::=” или “::-” - используется для создания только новых объектов. Если объект с таким именем был определен ранее, то произойдет ошибка компиляции.
  • :=” или “:-” - используется для создания новых объектов с возможностью перекрытия имен. Если локальный объект с таким именем был определен ранее, то новый объект будет его перекрывать.
  • =” - применяется только для присвоения значения уже существующим объектам. Если объект с указанным именем отсутствует, то возникает ошибка компиляции.
  • :=:” - Оператор не создает нового объекта, а обменивает значения уже существуюих (переменные должны иметь одинаковые/совместимые типы данных).

?????????????????????????????????????

  • []=” - добавляет новый элемент к контейнеру слева (аналог push_back): X []= v равносильно X.push_back(v). Для словаря добавляет позиционный элемент, для строки - символ/строку. Ширина строки RHS должна совпадать с типом строкового контейнера. Вложенный LHS (d['x'] []= v, d[0] []= v, d.field []= v) пока не реализован (ошибка компиляции). Именованный элемент словаря добавляется оператором присваивания d[name] = value (установка/добавление по ключу). Для словаря через оператор распаковки ... поддерживается слияние: d []= ... dict2 или d []= ... (a=1, b=2,) добавляет все элементы словаря-операнда (аналог extend/update); размер цели растёт на число элементов (статически контролируется для литерала и словаря с известным размером).

Использование трех разных видов операторов для создания/изменения объектов позволяет более гибко контролировать подобные операции и выявлять логические ошибки в коде на более раннем этапе.

Например, при определении класса :NewClass2:

    :NewClass := :Class() { # Базовый класс
        filed ::= 2; # Однократная инициализация (статическоого) члена класса
        method() ::= {}; # Регистрация метода
    };
    :NewClass2 := :NewClass() {
        filed ::= 2; # Будет ошибка, т.к. field уже есть в базовом классе
        method() = {}; # Аналог override, т.к. method должен существовать в базовом классе
    };

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

    var := 1.0; # Создать новую переменную var без явного указания типа
    var := 100; # Присвоить новое значение уже существующей переменной
    printf(format:FmtChar, ...):Int32 ::= %printf...; 
# Создать новый или переопределить printf ???????????????????????????????????????????

Присваивание значения сразу нескольким переменным и оператор распаковки словаря

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...) связывает оставшиеся элементы; _... — извлечь элементы, остаток отбросить; _ — пропустить один. Каждая цель типизируется runtime-типом своего элемента; внутри цикла тип расширяется до максимального среди элементов (Bool/Int8 → Integer, float → Double). Assignment into existing variables uses = (a, b = ... d; or a, b = t;): targets must be declared earlier (:=), no new variables are created; otherwise a compile error. Assignment to a constant target (^) is an error. A target may carry an explicit type annotation (a:Int32, b := ... d; / a:Int32, b = t;) which fixes the declared variable type: for a tuple — int32_t c_a instead of auto; for a dictionary the variable is declared int32_t, while the element is extracted by its storage runtime type (std::any_cast<int64_t>(...), since Dict normalizes integers to int64_t). Without annotation the type is inferred. Nested destructuring (a, (b, c) := t;) is not supported — only flat variables. Named-key destructuring ({a: x, b: y} = d — positional only, by insertion order), rest-in-the-middle (a, rest..., c := seq — the rest... marker is allowed only as the last target) and a type annotation on a rest target (rest:MyDict... / rest:MyTuple... — the rest type is always inferred: Dict for a dictionary, a sub-tuple for a tuple; annotation is a compile error) are not supported. For a tuple, a rest target may not reuse the name of an existing variable (including the source itself — a, t... := t): unlike the dictionary spread, where item, dict... := ... dict is a legit source-mutation idiom, a tuple rest is not a mutation, so such reuse is a compile error. On a runtime element shortage (source of compile-time-unknown size) execution terminates with a controlled abort + diagnostic — no silent default (None/zero) for any target type (including Any). If a dictionary element type is not inferable (e.g. a dictionary parameter), the target outside a loop is typed std::any with a warning (symmetric to the in-loop widening) — never silently.

Пример реализации цикла foreach для суммирования всех элементов словаря (или одномерного тензора) с использованием оператора раскрытия словаря (списка):

    summa := 0;
    dictionary := (1,2,3,4,5,);
    @while( dictionary ) {
        # Первый элемент словаря перемещается в item; суффикс `dictionary...` связывает
        # оставшиеся элементы, мутируя источник (цикл завершается, когда словарь опустеет)
        item, dictionary... := ... dictionary; 
        summa += item;
    };

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

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

  • :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).