Функции
Определение функции происходит с помощью операторов создания объектов, а имя функции должно соответствовать правилам именования, после которого указываются круглые скобки. Телом обычной функции должен быть блок кода, который всегда находится в пространстве имен определения функции.
Все функции поддерживают аргументы по умолчанию. При определении функции вначале перечисляются обязательные аргументы, далее идут аргументы со значениями по умолчанию, где имя аргумента отделяется от значения по умолчанию знаком равно “=”. Если функция допускает обработку произвольного количества аргументов, то последним в списке параметров должно быть указано многоточие “…”.
У аргументов функции может быть указан тип данных. Тип возвращаемого значения указывается после закрывающей скобки. У аргументов и у типа возвращаемого значения можно указать сразу несколько допустимых типов данных. Для этого их необходимо перечислить через запятую и заключить в угловые скобки, т.е.:
func(arg:<:Int8, :Int16, :Int32>): <:Int8, :Int16, :Int32> ::= {$arg*$arg};
NewLang на уровне синтаксиса поддерживает несколько видов функций:
Обычные функции
Обычная функция — это именно обычная функция в понимании С/С++ без каких либо ограничнений. Обычная функция может содержать совершенно любой код, включая проверки условий, циклы, вызовы других функций и т.д. Создание обычной функции происходит с помощью оператора ::=.
hello(str) ::= {
$print('call: "%s"', $str);
};
hello('Привет, мир!');
Вывод:
> hello
> call: "Привет, мир!"
Чистые функции
Чистая функция — это тоже обычная функция, только в том смысле, какой в него вкладывает функциональное программирование. Вызов чистой функции не влияет на состояние программы и они не имеют побочных эффектов. Чистые функции возвращают значения только на основе входных аргументов и не изменяют их. Признаки чистых функций:
- Всегда возвращают одинаковые значения для одинаковых аргументов.
- Не имеют побочных эффектов — не изменяют состояние программы за пределами своей области видимости.
- Не имеют побочных эффектов на аргументы, которые переданы им по ссылке и не изменяют их значения.
- Не зависят от состояния программы, не используют глобальные переменные или переменные, которые могут изменяться во время выполнения программы.
Создание чистой функции происходит с помощью оператора ::-.
sum(arg1, arg2) ::- {$arg1+$arg2}; # Простая функция, которая возвращает сумму аргументов
Анонимная функции
Анонимная функция - функция, которая объявлена в месте использования и не имеют собстенного имени (уникального идентификатора). Анонимная функиця может быть как лямбда-выражением (замыканием), так и обычной или чистой функций. В последнем случае в качестве имени функции испольуется символ нижнего подчеркивания.
Анонимные функции обычно используются для однократного применения непосредственно в месте её определения, где она передается в качестве аргумента в другую функцию, например при создании итератора или при реализации концепции многопоточного или асинхронного программирования.
Пример использования чистой функции: _(value)::-{ $value % 2 }
$list ::= (0, 1, 2, 3, 4, 5, 6, 7, 8, 9,);
$odd ::= @iter( $list, _(value)::-{ $value % 2 } );
Лямбда-выражения (замыкания)
Лямбда-выражение используется для объявления анонимных функций по месту их применения и допускает замыкание (closure) на лексический контекст, в котором это выражение использовано.
Замыкание (англ. closure) в программировании — функция первого класса, в теле которой присутствуют ссылки на переменные, объявленные вне тела этой функции в окружающем коде и не являющиеся её параметрами.
С помощью лямбда-выражений можно объявлять функции в любом месте кода. Синатсис лямбда-выражений очень похож на анонимные функции в языке C++:
[ captures ] (arguments) : return-type
{
body
};
Лямбда выражение (замыкание) по определению не может быть чистой функцией.
Лямбда выражение можно вызвать сразу после определения,
для этого необходимо указать круглые скобки сразу после закрывающей фигурной скобки: [](){ ... }
();
Сопрограммы (coroutines)
Если при определении функции в качестве её тела указать лямбда-выражение, то такая функция будет сопрограммой (coroutine).
Сопрограмма — это функция, которая может приостановить выполнение, чтобы возобновить его позже. Сопрограммы не имеют стека: они приостанавливают выполнение, возвращая управление вызывающей стороне, а данные, необходимые для возобновления выполнения, хранятся отдельно от стека.
Это позволяет использовать последовательный код, который выполняется асинхронно (например, для обработки неблокирующего ввода-вывода без явных обратных вызовов), а также с помощью сопрограмм реализуются генераторы и ленивые вычисления.
Переопределение и перегрузка функций
В NewLang разрешно переопределение и перегрузка функций. Но так как сам язык позволяет вызывать нативные С/С++ функции напрямую из кода, а так же за счет расширенного синтаксиса делать вставки С/С++ кода прямо в исходном тексте программы, то переопределение и перегрузка функций имеет свои особенности.
-
Переопределение (overriding) функций - фундаментальный принцип объектно-ориентированного программирования, в котором подкласс реализует определенный метод, объявленный в суперклассе.
-
Перегрузка (overloading) функций - создание нескольких функций с одним и тем же именем, но разным набором параметров. Компилятор на этапе компиляции программы на основании параметров выбирает нужный тип функции.
Переопределение функций (overriding)
В NewLang переопределение функций допускается не только для методов класса (т.е. виртуальных методов), но и для обычных функций. В случае обычных функций это похоже на механизм 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:Int16, arg2:Int16):Int16 = ... ; # ОК
func(arg:Integer, ...):Integer = ... ; # ОК
Для создания нескольких функций с одним именем, но разными числом или типом аргументов используется перегрузка (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
};