Разделы сайта находятся в разработке.
Материалы могут быть неполными или содержать ошибки. Содержимое сайта будет синхронизировано с реализацией при выходе следующей версии, тогда как текущая (0.6.0) предназначена для прототипирования синтаксиса и создания песочницы.
Функции
Функция определяется операторами создания объектов, имя функции должно соответствовать правилам именования и завершаться круглыми скобками. Телом обычной функции служит блок кода, находящийся в пространстве имён её определения.
У аргументов и у типа возвращаемого значения указывается тип (arg:Type).
Наборы допустимых типов
Несколько допустимых типов задаются набором типов — математическими операторами
+ (объединение) и - (исключение). Набор определяется один раз как именованный тип:
combi ::= :Int8 + :Int16 + :Int32; # именованный набор
Функция, у которой параметр имеет тип-набор, разворачивается в N определений (по одному на член набора) с общим телом; вызов разрешается по типу аргумента, как при явной перегрузке:
sq(x:combi):Int64 := { @return :Int64(x) * :Int64(x); };
# эквивалентно трём определениям: sq(:Int8), sq(:Int16), sq(:Int32)
sq(:Int16(3)); # выберет определение с параметром :Int16
Набор можно указать и inline — непосредственно в позиции типа (сигл : идёт сразу перед ():
sq2(x:(:Int8 + :Int16)):Int64 := { @return :Int64(x) * :Int64(x); };
Один и тот же именованный набор может стоять и в параметре, и в возврате — тогда определения сопоставляются позиционно (i-й член набора — и для параметра, и для возврата):
combi ::= :Int8 + :Int16;
idk(x:combi):combi := { @return x; }; # 2 определения: (:Int8)->:Int8, (:Int16)->:Int16
Правила и ограничения:
- В прототипе допустим не более одного различного набора (иначе — ошибка о комбинаторной сложности); один и тот же набор может использоваться в нескольких позициях.
- Набор только в возврате недопустим: перегрузки различались бы лишь типом возврата (в C++ перегрузки по возврату нет). Используйте тот же набор и в параметре.
- Inline-набор — только тип, не выражение (нельзя присвоить/передать как значение).
- Исключение
-пока не реализовано (явная ошибка). - Членами набора могут быть простые типы (шаблонные типы в наборе пока не поддерживаются).
TrustLang на уровне синтаксиса поддерживает несколько видов функций.
Обычные функции
Обычная функция ничем не ограничена в смысле C/C++: может содержать условия, циклы, вызовы других
функций и т. д. Создаётся оператором :=:
hello(str:StrChar) := {
@print('call: {}\n', str);
};
@main() := {
hello('Привет, мир!');
};
Именованные и опциональные параметры
Именованные параметры позволяют передавать аргументы по имени (с префиксом «.»), а не по позиции:
func(width:Int32, height:Int32) := { ... };
func(.width=640, .height=480);
func(.height=480, .width=640); # порядок не важен
Имя именованного аргумента не перекрывается макросом препроцессора (см. Именование объектов).
Опциональные параметры имеют значение по умолчанию и могут быть опущены при вызове: имя аргумента
отделяется от значения знаком =. Сначала перечисляются обязательные параметры, затем — со значениями
по умолчанию. Функция, допускающая произвольное число аргументов, заканчивает список параметров
многоточием ....
Чистые функции
Чистая функция не имеет побочных эффектов: возвращает результат только по своим аргументам и не
изменяет состояние программы. Синтаксис декларации ::- не реализован (см. Статус);
к понятию чистоты относится и требование иммутабельности аргументов по ссылке (см.
Именование объектов).
Сопрограммы (coroutines)
Сопрограмма — функция-замыкание, способная приостанавливать и возобновлять выполнение (кооперативная многозадачность). В текущей версии не реализовано — см. Статус и раздел Многопоточность и асинхронность.
Анонимные функции
Анонимная функция объявляется в месте использования и не получает собственного имени (уникального
идентификатора). В TrustLang анонимная функция задаётся лямбда-выражением и
представляет собой значение функционального типа std::function<...>: его можно сохранить в
переменной, передать аргументом (функция обратного вызова, предикат) или вызвать на месте.
Лямбда-выражения (замыкания)
Лямбда-выражение объявляет анонимную функцию по месту применения:
[ captures ] (params) : Ret { body }
captures— список захватов: только имена ранее объявленных переменных, по значению (копия). Пустые скобки[]— без захвата. Ссылочный захват[&x]в текущей версии не поддерживается (диагностика об ошибке).params— параметры как у обычной функции (x:Type, тип опционален).: Ret— тип возврата; при отсутствии выводится из тела.body— блок кода{ ... }; значение блока — результат лямбды.
Лямбда-значение типизируется функциональным типом и может храниться/передаваться. Возврат значения —
как у обычной функции (@return / именованная форма), лямбда внутренне имеет собственную метку
возврата:
f := [](x:Int32):Int32 { @return x * x; };
y := f(5);
Немедленный вызов
Лямбду можно вызвать сразу после определения. Каноническая форма — обёрнутая:
y := ( [](x:Int32):Int32 { @return x * x; } )(5);
Прямая форма [](x){... }(5) (без обёртки) не поддерживается и выдаёт ошибку с подсказкой
использовать ( lambda )(args).
Не поддерживается (первая итерация)
Ссылочный захват [&x], неявный захват [=]/[&], захват со значением/типом
([x = expr], [x:Type]) и захват некопируемых значений (эксклюзивное владение unique) —
не поддерживаются и дают явную диагностику. «Замыкание по определению не может быть чистой
функцией» — чистая функция (::-) в языке пока не реализована.
Переопределение и перегрузка функций
Поскольку TrustLang позволяет вызывать нативные C/C++-функции напрямую и делать вставки C++-кода, переопределение и перегрузка функций имеют свои особенности.
- Переопределение (overriding) — реализация методом подкласса метода, объявленного в суперклассе (фундаментальный принцип ООП).
- Перегрузка (overloading) — создание нескольких функций с одним именем, но разным набором параметров; нужный вариант выбирается на этапе компиляции по параметрам.
Переопределение (overriding)
Переопределение допускается не только для методов класса, но и для обычных функций (механизм, похожий
на LD_PRELOAD, реализованный средствами самого языка). Чтобы функцию можно было переопределить, при её
создании не должен быть указан признак иммутабельности. Если суперкласс объявил метод мутабельным
(виртуальным), а подкласс реализует его с признаком иммутабельности, это равносильно override final
(без дальнейшего переопределения):
base::func() := { ... }; # virtual
class1::func() = { ... }; # override
class2::func^() = { ... }; # override final
Переопределение идёт по имени функции без учёта аргументов, однако новая функция должна иметь аргументы, совместимые с типом аргументов исходной функции.
Перегрузка (overloading)
Перегрузка — несколько функций с одним именем и разными сигнатурами (набором типов параметров);
нужный вариант выбирается статически на этапе компиляции по типам аргументов. Точный дубль
сигнатуры — ошибка; если подходящего варианта нет — ошибка no matching overload, если несколько
вариантов несравнимы — ambiguous.
Перегрузка свободных функций и методов пользовательских классов/Struct поддержана:
f(a:Int32):Int32 := { @return a + 1; };
f(a:Int64):Int32 := { @return 100; };
@main() := {
x : Int32 := 5;
@print('{}\n', f(x)); # выбран f(a:Int32)
@print('{}\n', f(7:Int64)); # выбран f(a:Int64)
};
:C ::= :Class{
base:Int32 := 10;
f(a:Int32):Int32 := { @return a + 1; }; # перегрузка МЕТОДА
f(a:Int64):Int32 := { @return base + 90; };
};
Выбор варианта — по типам аргументов: точное совпадение предпочтительнее числового расширения
(промоция/конверсия), параметр :Any принимает любой тип (низший приоритет); при нескольких
несравнимых лучших — ambiguous.
Перегрузку (выбор сигнатуры и допустимость/приведение аргументов) для функций и методов TrustLang всегда выполняет компилятор — и при единственной сигнатуре тоже. Делегирование C++-слою применяется только к нативным функциям/методам (их типы заданы явно нативными, а имя символа фиксировано внешней библиотекой).
Перегрузка по-прежнему возможна и для нативных функций (все типы аргументов указываются явно и являются нативными типами); нативные функции также доступны для прямого вызова из языков C/C++:
%func(arg:Int8):None := {}; # void func(int8_t)
%func(arg:Byte):None := {}; # void func(uint8_t)
%func(arg:Int64):None := {}; # void func(int64_t)