Функции
Определение функции происходит с помощью операторов создания объектов, а имя функции должно соответствовать правилам именования, после которого указываются круглые скобки. Телом обычной функции должен быть блок кода, который всегда находится в пространстве имен определения функции.
Все функции поддерживают аргументы по умолчанию. При определении функции вначале перечисляются обязательные аргументы, далее идут аргументы со значениями по умолчанию, где имя аргумента отделяется от значения по умолчанию знаком равно “=”. Если функция допускает обработку произвольного количества аргументов, то последним в списке параметров должно быть указано многоточие “…”.
У аргументов функции может быть указан тип данных. Тип возвращаемого значения указывается после закрывающей скобки. У аргументов и у типа возвращаемого значения можно указать сразу несколько допустимых типов данных. Для этого их необходимо перечислить через запятую и заключить в угловые скобки, т.е.:
func(arg:<:Int8, :Int16, :Int32>): <:Int8, :Int16, :Int32> := {$arg*$arg};
TrustLang на уровне синтаксиса поддерживает несколько видов функций:
Обычные функции
Обычная функция — это именно обычная функция в понимании С/С++ без каких либо ограничнений. Обычная функция может содержать совершенно любой код, включая проверки условий, циклы, вызовы других функций и т.д. Создание обычной функции происходит с помощью оператора :=.
hello(str:StrChar) := {
@print('call: {}\n', str);
};
@main() := {
hello('Привет, мир!');
};
Вывод:
> hello
> call: "Привет, мир!"
Чистые функции
Чистая функция — это тоже обычная функция, только в том смысле, какой в него вкладывает функциональное программирование. Вызов чистой функции не влияет на состояние программы и они не имеют побочных эффектов. Чистые функции возвращают значения только на основе входных аргументов и не изменяют их. Признаки чистых функций:
- Всегда возвращают одинаковые значения для одинаковых аргументов.
- Не имеют побочных эффектов — не изменяют состояние программы за пределами своей области видимости.
- Не имеют побочных эффектов на аргументы, которые переданы им по ссылке и не изменяют их значения.
- Не зависят от состояния программы, не используют глобальные переменные или переменные, которые могут изменяться во время выполнения программы.
Создание чистой функции происходит с помощью оператора ::-.
⚠️ НЕ РЕАЛИЗОВАНО: оператор `::-` (чистая функция) не реализован в текущей версии (синтаксическая ошибка)
sum(arg1, arg2) ::- {$arg1+$arg2}; # Простая функция, которая возвращает сумму аргументов
Сопрограммы (coroutines)
⚠️ НЕ РЕАЛИЗОВАНО: сопрограммы/корутины и макрос @co_yield не реализованы в текущей версии: `@co_yield i` даёт ошибку 'Missing operator between @co_yield and i', а в include/trust/dsl.src соответствующие макросы закомментированы
Сопрограмма (coroutine) — это функция замыкание (closure) на лексический контекст, которая может приостановить выполнение, чтобы возобновить его позже.
[]generate:Int64(start:Int64, end:Int64) := {
i:Int64 := start;
@while(i < end) {
@co_yield i;
i += 1;
};
};
???? for (auto value: generate(0, 10)) { ???? std::cout « value « std::endl; ???? }
Для определения сопрограммы в качестве тела функции необходимо указать лямбда-выражение.
Сопрограммы не имеют стека: они приостанавливают выполнение, возвращая управление вызывающей стороне, а данные, необходимые для возобновления выполнения, хранятся отдельно от стека.
Это позволяет использовать последовательный код, который выполняется асинхронно (например, для обработки неблокирующего ввода-вывода без явных обратных вызовов), а также с помощью сопрограмм реализуются генераторы и ленивые вычисления.
Анонимная функции
Анонимная функция - функция, которая объявлена в месте использования и не имеют собстенного имени (уникального идентификатора). Если анонимная функиця является обычной или чистой функций, то в качестве имени испольуется символ нижнего подчеркивания.
Для лямбда-выражения (замыкания) символ нижнего подчеркивания в качестве имени функции можно не указывать.
Анонимные функции обычно используются для однократного применения непосредственно в месте её определения, где она передается в качестве аргумента в другую функцию, например при создании итератора или при реализации концепции многопоточного или асинхронного программирования.
Пример использования чистой функции: _(value)::-{ $value % 2 }
$list := (0, 1, 2, 3, 4, 5, 6, 7, 8, 9,);
$odd := @iter( $list, _(value)::-{ $value % 2 } );
$odd_lambda := @iter( $list, [](value){ $value % 2 } );
Лямбда-выражения (замыкания)
Лямбда-выражение используется для объявления анонимных функций по месту их применения и допускает замыкание (closure) на лексический контекст, в котором это выражение использовано.
Замыкание (англ. closure) в программировании — функция первого класса, в теле которой присутствуют ссылки на переменные, объявленные вне тела этой функции в окружающем коде и не являющиеся её параметрами.
С помощью лямбда-выражений можно объявлять функции в любом месте кода. Синатсис лямбда-выражений очень похож на анонимные функции в языке C++:
[ captures ] (arguments) : return-type
{
body
};
Лямбда выражение (замыкание) по определению не может быть чистой функцией.
Лямбда выражение можно вызвать сразу после определения,
для этого необходимо указать круглые скобки сразу после закрывающей фигурной скобки: [](){ ... }();
Переопределение и перегрузка функций
В TrustLang разрешно переопределение и перегрузка функций. Но так как сам язык позволяет вызывать нативные С/С++ функции напрямую из кода, а так же за счет расширенного синтаксиса делать вставки С/С++ кода прямо в исходном тексте программы, то переопределение и перегрузка функций имеет свои особенности.
-
Переопределение (overriding) функций - фундаментальный принцип объектно-ориентированного программирования, в котором подкласс реализует определенный метод, объявленный в суперклассе.
-
Перегрузка (overloading) функций - создание нескольких функций с одним и тем же именем, но разным набором параметров. Компилятор на этапе компиляции программы на основании параметров выбирает нужный тип функции.
Переопределение функций (overriding)
В TrustLang переопределение функций допускается не только для методов класса (т.е. виртуальных методов), но и для обычных функций. В случае обычных функций это похоже на механизм LD_PRELOAD, реализованный внутри самого языка программирования без посторонних инструментов.
Чтобы была возможность переопределить реализацию функции, при её создании не должен быть указан признак иммутабельности (неизменяемости).
Если метод класса ранее был определен как мутабельный (вирутальный), а подкласс реализует данный метод с признаком иммутабельности,
это равносильно определению метода как override final, т.е. без возможности переопределения данной функции в остальных производных классах.
base::func () := { ... }; # virtual
class1::func () = { ... }; # override
class2::func^ () = { ... }; # override final
Переопределение (overriding) функций происходит только по имени функции без учета её аргументов. Однако новая функция должна иметь аргументы, совместимые с типом аргумента первоначальной функцией.
func(arg:Int8):Int8 := ... ;
func(arg:Int16):Int16 = ... ; # ОК Int8 -> Int16
func(arg:String):Int16 = ... ; # Ошибка String =/= Int16
# Но можно переопределить вот следуюим образом
func(arg:<:String, ...>):String = ... ;
# в явном виде добавляем новые тип для аргумента, который будет использовтаься при сопоставлении
# во время поиском нужного фарината функции при её вызове
func(arg:Int16, arg2:Int16):Int16 = ... ; # ОК
func(arg:Integer, ...):Integer = ... ; # ОК
# Так же можно определить нативную функция для прамяго вызова из С++
%func(arg: Int32): Int32 := { # int32_t func(int32_t)
};
Для создания нескольких функций с одним именем, но разными числом или типом аргументов используется перегрузка (overloading) функций.
Перегрузка функций (overloading)
Перегрузка (overloading) функций - это определение функции с одним и тем же именем, но разными количеством и/или типом аргументов. Перегрузка (overloading) возможна только для функций, которые объявлены как нативные. У нативной функции все типы её аргументов должны быть указаны в явном виде и тоже должны быть нативными типами.
Нативные функции доступны для прямого вызова из языков С/С++.
%func(arg: Int8): None := { # void func(int8_t)
};
%func(arg: Byte): None := { # void func(uint8_t)
};
%func(arg: Int64): None := { # void func(int64_t)
};
%func(arg^: Float): Float := { # float func(float const)
};
%func^(arg: Double): Double := { # double func(double) const
};