\chapter{Функции, написанные пользователями}
\label{chap:user-funcs}

Hansl изначально предоставляет достаточно широкий набор
предопределенных функций для управления переменными всех видов;
предыдущие главы содержат несколько примеров. Однако также возможно
расширить встроенные возможности Hansl путем определения
дополнительных функций. Вот как выглядит пользовательская функция:

\begin{flushleft}
\texttt{function \emph{type} \emph{funcname}(\emph{parameters})}\\
   \quad \texttt{\emph{function body}}\\
\texttt{end function}
\end{flushleft}
Первая строка определения функции содержит все следующие элементы в
строгом порядке:
\begin{enumerate}
\item  Ключевое слово \texttt{function}.
\item \texttt{\emph{type}}, где указывается тип значения,
  возвращаемого функцией, если таковое имеется. Тип должен быть либо
  одим из \texttt{void} (если функция ничего не возвращает):
  \texttt{scalar}, \texttt{series}, \texttt{matrix}, \texttt{list},
  \texttt{string}, \texttt{bundle}, либо одним из типов array,
  т.е. \texttt{bundles}, \texttt{lists}, \texttt{matrices} and
  \texttt{strings};
\item \texttt{\emph{funcname}}: уникальный идентификатор
  функции. Имена функций бывают длиной до 31 символа; они должны
  начинаться с буквы и могут содержать только буквы, цифры и символ
  подчеркивания. Они не могут совпадать с именами собственных команд
  gretl или его функций.
\item \texttt{\emph{parameters}}: параметры функции в виде списка,
  разделенного запятыми и заключенного в круглые скобки . Примечание:
  параметры --- это единственный способ передать что-либо извне по
  средством функции, так как в Hansl нет глобальных переменных.
\end{enumerate}

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

\begin{center}
\begin{tabular}{ll}
  \multicolumn{1}{c}{Тип} & 
  \multicolumn{1}{c}{Описание} \\ [4pt]
  \texttt{bool}   & скалярная переменная, действующая как логический переключатель \\
  \texttt{int}    & скалярная переменная, действующая как целое число  \\
  \texttt{scalar} & скалярная переменная\\
  \texttt{series} & серии данных (см. гл. ~\ref{sec:series})\\
  \texttt{list}   & именованный список серий (см. гл. ~\ref{sec:lists})\\
  \texttt{matrix} & матрица или вектор \\
  \texttt{string} & строковая переменная или строковый литерал \\
  \texttt{bundle} & универсальный контейнер \\
  \texttt{matrices} & массив матриц \\
  \texttt{bundles}  & массив пакетов \\
  \texttt{strings}  & массив строковых переменных
\end{tabular}
\end{center}
Каждый элемент в списке параметра должен включать два термина:
спецификатор типа и имя по которому данный параметр должен быть найден
в самой функции.  Тело функции (function body) содержит (почти)
произвольный код Hansl, который должен вычислять возвращаемое функцией
значение. Любая переменная, объявленная внутри функции, определяется
как локальная (local), поэтому она перестанет существовать сразу после
завершения функции.  Команда возврата \cmd{return} используется, чтобы
остановить выполнение кода внутри функции и перенести ее результат в
вызывающий код. Обычно это происходит в конце тела функции, но это
необязательно. Определение функции должно заканчиваться выражением
\verb|end function| в отдельной строке.

\tip{Будьте осторожны: в отличие от некоторых других языков (например,
  Matlab или GAUSS), вы не можете напрямую получить несколько
  результатов из функции. Однако вы можете получить пакет и наполнить
  его любым количеством объектов.}

Чтобы понять, как пользовательские функции работают на практике,
рассмотрим простой пример:
\begin{code}
function scalar quasi_log (scalar x)
   /* popular approximation to the natural logarithm
     via Pade polynomials 
   */
   if x < 0
      scalar ret = NA
   else 
      scalar ret = 2*(x-1)/(x+1)
   endif
   return ret
end function

loop for (x=0.5; x<2; x+=0.1)
   printf "x = %4.2f; ln(x) = %g, approx = %g\n", x, ln(x), quasi_log(x)
endloop
\end{code}

Приведенный выше код вычисляет рациональную функцию
\[
  f(x) = 2 \cdot \frac{x-1}{x+1} ,
\]
которая обеспечивает неполохое приближение к натуральному логарифму в
окрестности 1. Некоторые комментарии к коду:
\begin{enumerate}
\item Поскольку функция предназначена для возврата скаляра, мы
  помещаем ключевое слово «\cmd{scalar} после начального
  «\cmd{function}».
\item В этом случае список параметров содержит только один элемент: он
  называется \texttt{x} и указывается как скаляр.
\item На следующей строке начинается определение функции; тело
  включает комментарий и блок \texttt{if} .
\item Функция завершается выводом вычисленного значения \texttt{ret}.
\item Строки под определением функции демонстрируют простой пример
  использования функции.  Обратите внимание, что в \cmd{printf}, две
  функции \cmd{ln()} и \cmd{quasi\_log()} неотличимы с синтаксической
  точка зрения, хотя первая является родной функцией, а вторая
  создается пользователем.
\end{enumerate}

При детальном использовании Hansl вы можете написать несколько
функций, некоторые из которых могут быть довольно длинными. Чтобы
избежать загромождения вашего скрипта определениями функций, Hansl
предоставляет команду \cmd{include}: вы можете поместить свои
определения функций в отдельный файл (или пакет файлов) и читать их в
по мере необходимости. Например, предположим, что вы сохранили
определение \verb|quasi_log()|: в отдельном файле с именем
\verb|quasilog_def.inp|. Тогда приведенный выше код можно было бы
записать более компактно как:

\begin{code}
include quasilog_def.inp

loop for (x=0.5; x<2; x+=0.1)
   printf "x = %4.2f; ln(x) = %g, approx = %g\n", x, ln(x), quasi_log(x)
endloop
\end{code}
Более того, команды \cmd{include} могут быть вложенными.


\section{Передача параметров и возвращаемые значения}
\label{sec:params-returns}

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

\begin{code}
function void f(scalar x)
    x = x*2
    print x
end function

scalar x = 3
f(x)
print x
\end{code}
Если применить данный код, то получится следующее:
\begin{code}
              x =  6.0000000
              x =  3.0000000
\end{code}
Первая операция вывода \cmd{print} выполняется внутри функции и
отображаемое значение равно 6, потому что входное значение \verb|x|
удваивается; однако то, что действительно удваивается, --- это просто
копия исходного \verb|x|: это демонстрируется второй операцией
\cmd{print}. Если вы хотите, чтобы функция изменяла свои аргументы, вы
должны использовать указатели.

% It must be said that copying the contents of the incoming parameter to
% a local version may have a sizeable impact on computational speed when
% the object is large (such as a 1000 $\times$ 1000 matrix). In order to
% avoid this, you may prepend the \texttt{const} modifier to the
% parameter type to alert \app{gretl} that that object is not going be
% modified inside the function, so the computational cost of storing an
% extra copy may be skipped. Consult the \GUG{} for further details.

\subsection{Указатели}

Каждый из спецификаторов типа, за исключением списка \texttt{list},
может быть изменен путем добавления звездочки перед ним к
соответствующему имени параметра, как в
  
\begin{code}
function scalar myfunc (matrix *y)
\end{code}
Это указывает на то, что требуемый аргумент не является простой
матрицей, а скорее указателем на матрицу, или другими словами, адресом
памяти, в которой хранится переменная.  Людям, незнакомым с языком
программирования C, это может показаться немного загадочным, поэтому
разрешите объяснить на следующем отвлеченном примере, как работают
указатели.

Предположим, вы открыли парикмахерскую. В идеале ваши клиенты заходили
в ваш салон, садились на стул и хотели, чтобы им или подстригли волосы
или побрили бороду. Теперь предположим, что местные правила запрещают
вам изменять кого-либо, кто переступает порог вашего салона. Конечно,
тут бы вашему бизнесу пришел конец, если люди бы выходили из вашего
салона совершенно без изменений внешности. Тем не менее, у вас есть
простой способ обойти это ограничение: ваши клиенты могут приходить к
вам в парикмахерскую, называть свой домашний адрес и уходить. Тогда
никто не мешает вам пойти к ним домой и заняться своим делом. При этом
вы соблюдаете все законы, потому что никаких изменений с посетителями
на территории вашего салона не происходит.

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

В Hansl (в отличие от C) вам не нужно проявлять особую осторожность в
самой функции, чтобы различать переменную и ее адрес\footnote{В C это
  будет называться разыменованием указателя (\emph{dereferencing}). В
  Hansl это различие не имеет значения, потому что нет эквивалента
  работы с самим предоставленным адресом, как в C.}, вы просто
используете имя данной переменной. Чтобы все же увидеть адрес
переменной, когда вы вызываете функцию, используйте оператор
амперсанда (\verb|&|). Например, следующий код
\begin{code}
function void swap(scalar *a, scalar *b)
    scalar tmp = a
    a = b
    b = tmp
end function

scalar x = 0
scalar y = 1000000
swap(&x, &y)
print x y
\end{code}
выдает такие значения:
\begin{code}
              x =  1000000.0
              y =  0.0000000
\end{code}
Итак, \texttt{x} и \texttt{y} фактически поменялись местами. Как это
получилось?

Сначала у вас есть определение функции, в котором аргументы являются
указателями на скаляры. Внутри тела функции, различие спорно,
поскольку \verb|a| означает «скаляр, который вы найдете по адресу,
заданному первым аргументом» (и то же самое для \verb|b|). Остальная
часть функции просто меняет местами \verb|a| и \verb|b| с помощью
локальной временной переменной.  Вне функции мы сначала инициализируем
два скаляра: \texttt{x} равно 0, \texttt{y} равно большому
числу. Когда вызывается функция, она передается в виде аргументов
\verb|&a| и \verb|&b|, которые Hansl идентифицирует как «адрес» для
скаляров \texttt{a} и \texttt{b} соответственно.

Написание функции с аргументами-указателями имеет два основных
последствия: во-первых, как мы только что видели, можно изменять
аргументы функции. Во-вторых, это позволяет избежать вычислительных
затрат на необходимость выделения памяти, чтобы копировать аргументы и
выполнять сами операции копирования; такие затраты пропорциональны
длине аргумента. Следовательно, для аргументов матрицы этот способ
отлично поможет писать более быстрые функции, так как создание копии
большой матрицы может отнимать достаточно много
времени\footnote{Однако, квалификатор const дает тот же самый
  результат. Более подробную информацию см. в Руководстве пользователя
  Gretl.}.

\subsection{Расширенная передача параметров и опциональные аргументы}

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

\begin{enumerate}
\item Для использования графического интерфейса к каждому параметру
  может быть прикреплена описательная строка.
\item Для некоторых типов параметров существует специальная
  синтаксическая конструкция, гарантирующая, что их значение
  ограничено; например, вы можете указать, что скалярный аргумент
  является положительным, или ограниченным заранее заданным
  диапазоном.
\item Некоторые аргументы можно сделать необязательными.
\end{enumerate}
Подробное обсуждение данных функций слишком объемно, чтобы уместиться
в этом документе, и заинтересованный читатель может обратиться к главе
«Пользовательские функции» Руководства пользователя Gretl. Здесь мы
просто покажем вам простой пример, который, как мы надеемся, не
требует пояснений. Он сочетает в себе вышеуказанные пункты 2 и
3. Предположим, у вас есть функция для создания смайлов, определяемая
как
\begin{code}
function void smileys(int times[0::1], bool frown[0])
    if frown
        string s = ":-("
    else
        string s = ":-)"
    endif
    
    loop times
        printf "%s ", s
    endloop
    
    printf "\n"
end function
\end{code}

И если запустить такой код 
\begin{code}
smileys()
smileys(2, 1)
smileys(4)
\end{code}

можно получить следующий результат:

\begin{code}
:-) 
:-( :-( 
:-) :-) :-) :-) 
\end{code}

\subsection{Вложение аргументов в пакеты}

В некоторых случаях функция может быть настолько сложной, что
потребуется большое количество аргументов. Хотя не существует
ограничений на количество аргументов, которые может содержать функция,
сложная структура функции становится неприятной для ее
использования. Некоторые языки программирования (например, R)
устраняют эту проблему, используя именованные аргументы, так что можно
вызывать функцию и указывать только несколько аргументов, которые
нужно использовать на самом деле, а остальным оставлять значения по
умолчанию.  В Hansl мы не используем именные аргументы, но применяемый
метод дает похожий результат: идея состоит в том, чтобы встроить
аргументы в пакет и использовать синтаксис пакета для обработки его
содержания.\footnote{``Пакет'' --- это термин, используемый в Hansl
  для обозначения ассоциативных массивов. Подробнее
  см. раздел\ref{sec:bundles}.}

Например, предположим, что вы хотите написать функцию для извлечения
подстроки из строки, и при желании использовать ее с заглавной буквы:
вы можете начать с чего-то вроде
\begin{code}
function string Sub(string s, scalar ini, scalar fin, bool capital)
    string ret = s[ini:fin]
    return capital ? toupper(ret) : ret
end function
\end{code}
так что вызов \texttt{Sub("nowhere", 4, 7, 1)} выдаст строку
\texttt{HERE}. Более сложная версия функции может иметь значения по
умолчанию, чтобы вы могли вызывать функцию в упрощенной форме. Если
использовать синтаксис, показанный в предыдущем подразделе, то можно
установить значения по умолчанию
\begin{code}
function string Sub(string s, scalar ini[1], scalar fin[3], bool capital[FALSE])
    string ret = s[ini:fin]
    return capital ? toupper(ret) : ret
end function
\end{code}
и вызов \texttt{Sub("nowhere")} выдаст строку \texttt{now}. Однако,
если мы хотим, чтобы строка была с заглавной буквы, мы должны были бы
установить четвертый параметр равным 1, поэтому для получения
\texttt{NOW} функция должна называться \texttt{Sub("nowhere", ,
  ,1)}. Если у вас более 5 или 6 параметров в подписи функции, запись
выглядит громоздко и становится неудобной в использовании.  Эту
проблему можно решить, если заключить аргументы 2–4 внутрь пакета, как
в

\begin{code}
function string Sub(string s, bundle opts)
    string ret = s[opts.ini:opts.fin]
    return opts.capital ? toupper(ret) : ret
end function
\end{code}
где вычисление возвращаемой строки происходит с использованием
содержимого пакета, поэтому вы можете вызвать функцию так:
\begin{code}
  bundle myopts = defbundle("ini", 1, "fin", 3, "capital", TRUE)
  string out = Sub("nowhere", myopts)
\end{code}
Обратите внимание, что две линии могут быть совмещены как: 
\begin{code}
  string out = Sub("nowhere", defbundle("ini", 1, "fin", 3,
                                        "capital", TRUE))
\end{code}
но в некоторых случаях удобно сохранить пакет опций как постоянный
объект, чтобы последующие вызовы функции происходили с постепенными
изменениями.  Использование этого механизма полезно, потому что он
вполне естественно поддается обработке значений по
умолчанию. Рассмотрим код ниже:
\begin{code}
function string Sub(string s, bundle opts_in[null])
    bundle opts = defbundle("ini", 1, "fin", 3, "capital", 0)
    if exists(opts_in)
        opts = opts_in + opts
    endif
    string ret = s[opts.ini:opts.fin]
    return opts.capital ? toupper(ret) : ret
end function
\end{code}
Давайте проанализируем тело функции по одной строке:

\begin{enumerate}
\item функция содержит только два аргумента: строку для обработки и
  пакет, который имеет значение по умолчанию \texttt{null} и поэтому
  может быть опущен.
\item пакет \texttt{opts} определен со значениями по умолчанию для
  скаляров \texttt{ini} и \texttt{fin} и для логической
  \texttt{capital}.
\item Если пакет был передан в качестве второго аргумента, то строка
  \begin{code}
     opts = opts_in + opts
  \end{code}
  заменяет ключи в функции \texttt{opts} на те, что присутствуют в
  \texttt{opts\_in} (с оператором \texttt{+}, левый пакет имеет
  приоритет). На этом этапе пакет \texttt{opts} будет содержать как
  стандартные, так и пользовательские настройки.
 
\item С этого момента все будет действовать, как указано выше.
\end{enumerate}
Это означает, что вызов \texttt{Sub("nowhere")} выдаст результат
\texttt{now}, но если бы мы хотели, чтобы результат начинался с
заглавной буквы, мы могли бы назвать функцию как
\begin{code}
  string out = Sub("nowhere", defbundle("capital", TRUE))
\end{code}
и строка \texttt{out} теперь содержит \texttt{NOW}. Идея
«дополнительных вариаций» для упомянутого выше пакета опций теперь
может быть использована, например, в следующем коде:
\begin{code}
  bundle myopts = defbundle("capital", TRUE)
  string out1 = Sub("nowhere", myopts)
  myopts.fin = 2
  string out2 = Sub("nowhere", myopts)
\end{code}
и выполнение сценария приведет к тому, что в строке \texttt{out1}
содержится \texttt{NOW}, а \texttt{out2}, содержит \texttt{NO}.  Более
того, уже в gretl версии 2021a, функция \texttt{defbundle} принимает
«сокращенный» синтаксис как \verb|_()| (см. документацию по
\texttt{defbundle} в Справочнике по командам Gretl для более подробной
информации). Это делает возможным вызвать функцию, написанную с
помощью этой техники, как если бы она имела именованные аргументы:
\begin{code}
  string out = Sub("nowhere", _(capital=1, fin=2))
\end{code}
Обратите внимание, что аргументы в \verb|_()| могут появляться в любом
порядке.

\section{Рекурсия}

Функции Hansl могут быть рекурсивными; ниже приводится обязательный
факториальный пример:
\begin{code}
function scalar factorial(scalar n)
    if (n<0) || (n>floor(n))
        # filter out everything that isn't a 
        # non-negative integer
        return NA
    elif n==0
        return 1
    else
        return n * factorial(n-1)
    endif
end function

loop i = 0 .. 6
    printf "%d! = %d\n", i, factorial(i)
endloop
\end{code}
Примечание: Для кого-то это может быть увлекательным, но на практике
намного лучше использовать предварительно приготовленную гамма-функцию
(или еще лучше, ее логарифм).

%%% Local Variables: 
%%% mode: latex
%%% TeX-master: "hansl-primer"
%%% End: 
