# Функции

Для определения функции используется следующий синтаксис:

('func' | 'функция') [<имя-функции>]
'('
   [<параметр-1> [',' <параметр-2> ... [',']]]
')'
[`строка с документацией`]
'{'
<тело-функции>
'}'

<параметр-i> = <именованный-параметр-i> | <вариадический-параметр>
<именованный-параметр-i> = <имя-параметра-i> ['=' <значение-по-умолчанию-i>]
<вариадический-параметр> = '...' [<имя-вариадического-параметра>]

Имя функции должно быть идентификатором. Параметры со значениями по умолчанию могут находиться только после параметров без значений по умолчанию. <Вариадический-параметр> может быть только один, и он должен находиться в самом конце списка параметров. Тело функции составляют 0 или более выражений или инструкций. Пример:

func f(x, y)
{
   return x + y;
}

При вызове функции для каждого именованного параметра в области видимости функции создаётся локальная переменная, и ей присваивается значение переданного при вызове аргумента:

func f(x, y)
{
   # x имеет значение 1, y имеет значение 2
   return x + y
}

f(1, 2);    # 3
f(1);       # исключение

Параметры функции могут иметь значения по умолчанию. Значение по умолчанию задается с помощью символа = и произвольного выражения после имени параметра. При вызове функции, если количество аргументов будет меньше количества параметров, параметры без соответствующих им аргументов получат значения по умолчанию. Значения по умолчанию вычисляются заново при каждом вызове функции.

# параметр x не имеет значения по умолчанию
# параметры y и z имеют значения по умолчанию 2 и 3
func f(x, y = 2, z = 3)
{
   return x + y + z
}

f(1)                       # возвращает 6, y и z получили значения
                           # по умолчанию (т.е. 1 и 2)
f(1, 20)                   # возвращает 24, y получил значение 20,
                           # z - по умолчанию
f(1, 20, 30)               # возвращает 51, все параметры получили
                           # значения из аргументов

var v = 1
func g(x = v)
{
   return x
}

g()                        # возвращает 1, параметру x присваивается
                           # значение по умолчанию из переменной v
v = 2
g()                        # возвращает 2, значение по умолчанию
                           # вычисляется при каждом вызове функции

Функция, которая имеет <вариадический-параметр>, называется вариадической. Вариадическая функция помимо именованных может принимать 0 или более дополнительных вариадических аргументов.

Если задано имя вариадического параметра, то все дополнительные аргументы собираются в массив (Array), и в области видимости функции создается локальная переменная с именем <имя-вариадического-параметра>, которая ссылается на этот массив. Первый вариадический аргумент будет первым элементом массива, второй - вторым и т.д. Если вариадических аргументов нет, массив будет пустым. Переменная параметры в этом случае не создается.

Если имя вариадического параметра не указано, для обращения к вариадическим аргументам нужно использовать специальную переменную параметры. Переменная параметры имеет тип Object, в ней для каждого вариадического аргумента создаётся запись с ключами: "1" - для первого аргумента, "2" - для второго и т.д. Если количество вариадических аргументов равно нулю, то объект параметры будет пустым.

Пример:

# задано имя вариадического параметра, все вариадические
# аргументы будут собраны в массив other
func sum(x, y, ...other)
{
   exists(параметры)             # false, переменная параметры
                                 # не определена

   var r = x + y
   forall (o in other)
      r += o
   return r
}

sum("a", "b")                    # "ab"
sum("a", "b", "c", "d")          # "abcd"


# имя вариадического параметра отсутствует, к вариадическм
# аргументам нужно обращаться с помощью переменной параметры
func sum(x, y, ...)
{
   exists(параметры)             # true, переменная параметры
                                 # определяется всегда, даже
                                 # если вариадических аргументов
                                 # нет

   var r = x + y;

   if (exists(параметры.1))
      r += параметры.1;

   if (exists(параметры.2))
      r += параметры.2;

   return r;
}

sum(1, 2, 3);     # 6
sum(1);           # исключение

Функции могут перегружать друг друга. Перегрузкой функции называется возможность определить несколько функций (перегрузок) с одним и тем же именем, но разными сигнатурами (т.е. разным количеством именованных параметров, параметров со значениями по умолчанию и вариадичностью). При вызове перегруженной функции будет вызвана та её перегрузка, которая лучше всего соответствует переданным аргументам. Например:

func f(x)         # №1
{
   return x + x;
}

func f(x, y)      # №2
{
   return x + y;
}

var x = f(5);     # 10, вызывается перегрузка №1
var y = f(1, 2);  # 3, вызывается перегрузка №2

Для поиска оптимальной перегрузки используется следующий алгоритм. Из всех возможных перегрузок функции выбираются те, которые могут быть вызваны с заданным количеством аргументов. Затем перегрузкам назначается приоритет:

  • максимальный приоритет имеет невариадическая перегрузка, у которой нет параметров со значениями по умолчанию

  • средний приоритет имеют невариадические функции, у которых есть параметры со значениями по умолчанию; внутри этой группы более приоритетными являются перегрузки, у которых меньше всего параметров со значениями по умолчанию

  • минимальный приоритет имеют вариадические перегрузки; среди них более приоритетными считаются те, которые имеют наибольшее количество именованных параметров

Наиболее подходящей считается перегрузка с наибольшим приоритетом. Если ни одна из перегрузок функции не может принять данное количество аргументов, возникает исключение.

func f(x, y, z)         # №1
{
}

func f(x, y, z = 1)    # №2
{
}

func f(x, ...)          # №3
{
}

f(1, 2, 3);             # выбирается перегрузка №1
                        # она имеет максимальный приоритет

f(1, 2);                # выбирается перегрузка №2 со средним приоритетом
                        # перегрузка №1 исключается из рассмотрения
                        # перегрузка №3 имеет минимальный приоритет

f(1)                    # выбирается перегрузка №3
                        # остальные перегрузки исключаются из рассмотрения

Если в тексте программы определены две или более функции с одинаковым именем и сигнатурой, то одна из них "перекрывает" все остальные. Это означает, что перекрытые функции "исчезают", вызвать их невозможно. Если перекрывающие функции находятся в одном файле, то выбирается та функция, которая определена последней по тексту файла. Если они находятся в разных файлах, то приоритет имеет та функция, файл которой был загружен последним. Пример:

func f()          # №1
{
   return 1;
}

func f()          # №2
{
   return 2;
}

var x = f();      # 2, вызывается функция №2
                  # функция №1 перекрыта, и вызвать её невозможно

Встроенные функции (конструкторы и пр.) перегрузить нельзя. Если определить функцию с таким же именем, как и встроенная, она перекроет встроенную функцию, даже если сигнатура пользовательской функции отличается от встроенной. Это сделано для того, чтобы при необходимости можно было заменить реализации стандартных функций.

Перегрузки функции являются экземплярами класса Overload. Свойство функции overload возвращает список видимых перегрузок функции.

Поскольку параметры функции ведут себя как локальные переменные, передача аргументов работает так же, как и присваивание: примитивные типы передаются по значению, остальные - по ссылке. Пример:

func f(a, b)
{
   a = 2;
   y.k = 2;
}

var x = 1;
var y = @{k: 1};

f(x, y);

# значение переменной x не изменилось
# значение y.k стало равным 2

Оператор return (вернуть) завершает выполнение текущей функции и передаёт управление вызвавшей её функции. Если после ключевого return указано выражение, то это выражение становится результатом вызова функции. Если выражение отсутствует, то результатом вызова будет специальное значение Empty, т.е. считается, что функция ничего не возвращает. Если в теле функции нет инструкции return, то после последнего выражения в теле функции вставляется неявный return без выражения. Пример:

func f()
{
   # функция возвращает 2
   return 1 + 1;
}

func g()
{
   # явный return без выражения, возвращается Empty
   return;
}

func m()
{
   var x = 1;
   # неявный return, возвращается Empty
}

var x = m();      # ошибка, функция m() ничего не возвращает

Если определить функцию внутри другой функции, то она все равно попадёт в общую область видимости пользовательских функций:

func f()
{
   func g()
   {
   }
}

g();           # будет вызвана функция g

Разрешено определять функции без имени. Такие функции называются анонимными. Определение анонимной функции является выражением (в отличие от определения именованной функции, которое является инструкцией):

var square = func (x) { return x * x }       # в переменной square содержится
                                             # ссылка на анонимную функцию

                                             # анонимную функцию можно вызвать через
                                             # имя переменной, которая содержит ссылку
                                             # на нее
var x = square(2);                           # x == 4

                                             # также анонимную функцию можно вызвать
                                             # непосредственно после ее определения
var y = func (x) { return x + 1 } (1);       # y == 2

Анонимные функции удобно использовать в качестве колбеков:

func transform(ary, callback)
{
   var result = @[]
   forall (e in ary)
      result.pushBack(callback(e))
   return result
}

var k = 1
var x = @[1, 2, 3]
var y = transform(x, func (e) { return e + k })

# y содержит все элементы x, увеличенные на 1, т.е. @[2, 3, 4]

В версии языка 2 и выше можно использовать сокращенный синтаксис для передачи блока кода функцию, а именно, выражение

<функция> '(' <аргументы> ')' '{' <тело-функции> '}'

эквивалентно

<функция> '(' <аргументы> ',' 'func' '{' <тело-функции> '}' ')'

например,

expectNoThrow()
{
   functionUnderTest()
}

эквивалентно

expectNoThrow(
   func ()
   {
      functionUnderTest()
   }
)

Так же как и именованные функции, анонимные функции не создают замыкания, и имеют динамическую область видимости (т.е. внутри нее видны все переменные вызывающей функции).

# Класс Func

# Методы

# hasOverload(...): Boolean (естьПерегрузка)

Принимает любое количество аргументов и возвращает true, если у функции есть хотя бы одна перегрузка, которая может принять такое количество аргументов, и false в противном случае. Пример:

func f(x, y)
{
   throw "Error"
}

f.hasOverload(1, 2);       # true
f.hasOverload();           # false
f.hasOverload(1, 2, 3);    # false

# Свойства

# name: String (имя)

Возвращает имя функции (текст идентификатора из определения функции). Пример:

func f() {}

var x = f;
x.name;           # "f"

# overloads: Array[Overload] (перегрузки)

Возвращает массив, который содержит все видимые перегрузки этой функции (т.е. перегрузки, которые не были перекрыты другими, и могут быть вызваны). Массив является копией списка перегрузок, его изменение не приводит к изменению самой функции.

# №1
func f() { return 1 }

# №2
func f(x) { return x }

# №3
func f() { return 2 }

f.overloads                   # возвращает массив перегрузок, два элемента
                              # один из них соответствует перегрузке №2,
                              # другой - №3; для перегрузки №1 записи нет,
                              # потому что она перекрыта перегрузкой №3

# docstring: String (докстрока)

Возвращает докстроку функции. Если у функции несколько перегрузок, то вернёт пустую строку.

func f() "f docs"
{
   ...
}

f.docstring;  # "f docs"

# documentation: String (документация)

Возвращает документацию для всех перегрузок функции, даже для кодовых. Пример:

func f()          `параметров: 0;`                {}
func f(x, ...)    `параметров: 1; вариадическая;` {}

f.documentation;    # f()
                    #   <path-to-script>:<line>:<column>
                    #   параметров: 0;
                    # f(x, ...)
                    #   <path-to-script>:<line>:<column>
                    #   параметров: 1; вариадическая;

# Класс Overload

# Свойства

# parametersCount: Real (количествоПараметров)

Возвращает количество позиционных параметров данной перегрузки.

func f(x, y) { }
func g(...) { }

f.overloads[1].parametersCount      # 2
g.overloads[1].parametersCount      # 0

# defaultArgumentsCount: Real (количествоАргументовПоУмолчанию)

Возвращает количество параметров данной перегрузки, которые имеют значения по умолчанию.

func f(x, y = 1, z = 2) { }
func g(x, y) { }

f.overloads[1].defaultArgumentsCount      # 2
g.overloads[1].defaultArgumentsCount      # 0

# variadic: Boolean (вариадическая)

Возвращает true, если данная перегрузка является вариадической, и false в противном случае.

func f() { }
func g(...) { }

f.overloads[1].variadic             # false
g.overloads[1].variadic             # true

# annotations: Array[Annotation] (аннотации)

Возвращает список аннотаций данной перегрузки. Если у нее нет аннотаций, возвращается пустой массив. Массив является копией списка аннотаций перегрузки, его изменение не приводит к изменению аннотаций самой перегрузки.

func f() { }

:[test]
func g() { }

f.overloads[1].annotations          # пустой массив
g.overloads[1].annotations          # массив с одной аннотацией test

# docstring: String (докстрока)

Возвращает докстроку для перегрузки:

func f()  "no params" {}
func f(x) "one param" {}

f.overloads[2].docstring   # "one param"