\chapter{Поток управления}
\label{chap:hp-ctrlflow}

Основными средствами управления потоком управления на языке Hansl
являются оператор \cmd{if} (условное выполнение), оператор \cmd{loop}
(повторное выполнение), модификатор \cmd{catch} (который включает
перехват ошибок, которые в противном случае остановили бы выполнение
скрипта), и команда \cmd{quit} (которая принудительно завершает
работу).

\section{Оператор \cmd{if}}

Условное выполнение в Hansl использует ключевое слово \cmd{if}. Его
наиболее полное использование выглядит следующим образом:
\begin{code}
if <condition>
   ...
elif <condition>
   ...
else 
   ...
endif  
\end{code}

Важно отметить:
\begin{itemize}
	
\item условие \texttt{<condition>} может быть любым выражением,
  вычисляющим скаляр: 0 интерпретируется как «ложь», ненулевое
  значение интерпретируется как «истина»; \texttt{NA} генерирует
  ошибку.
\item После \cmd{if} «then» явно не указывается; не существует
  ключевого слова \texttt{then}, как, например, в Pascal или Basic.
\item Строки \cmd{elif} и \cmd{else} необязательны: минимальная форма
  --- это просто \texttt{if} \dots{} \texttt{endif}.
\item Условные блоки такого типа могут быть вложены до максимальной
  глубины 1024.
\end{itemize}

Например:
\begin{code}
scalar x = 15

# --- simple if ----------------------------------
if x >= 100
   printf "%g is more than two digits long\n", x
endif

# --- if with else -------------------------------
if x >= 0
   printf "%g is non-negative\n", x
else
   printf "%g is negative\n", x
endif

# --- multiple branches --------------------------
if missing(x)
   printf "%g is missing\n", x
elif x < 0
   printf "%g is negative\n", x
elif floor(x) == x
   printf "%g is an integer\n", x
else
   printf "%g is a positive number with a fractional part\n", x
endif
\end{code}

В приведенном выше примере ключевое слово \cmd{elif} можно повторять,
в результате чего оператор \cmd{if} становится оператором
многостороннего перехода. В Hansl нет отдельного оператора
\cmd{switch} или \cmd{case}. С одним или более \cmd{elif}, Hansl
выполнит первый, для которого выполнено логическое условие, а затем
перейти к \cmd{endif}.

\tip{Пользователи Stata, будьте осторожны: оператор команды \cmd{if} в
  Hansl в корне отличается от варианта \texttt{if} в Stata: в
  последнем случае выбирается подвыборка наблюдений для некоторого
  действия, а в первом используется для определения того, должна ли
  серия условий выполняться или нет. «\cmd{If}» в Hansl это то, что
  Stata называет “branching \texttt{if}”.}


\subsection{Оператор тернарного запроса}

Помимо оператора \texttt{if} можно использовать оператор тернарного
запроса \texttt{?:} для выполнения условного присваивания на «микро»
уровне. Это выглядит так:
\begin{code}
result = <condition> ? <true-value> : <false-value>
\end{code}
Если \texttt{<condition>} оценивается как истинное (ненулевое), то
\texttt{<true-value>} присваивается результату, в противном случае
результат \texttt{result} будет содержать
\texttt{<false-value>}. \footnote{Некоторым читателям может быть
  интересно узнать, что оператор условного присваивания работает точно
  так же, как функция "если" \texttt{=IF()} в электронных таблицах.}
Это явно более компактно, чем \texttt{if} \dots{} \texttt{else}
\dots{} \texttt{endif}. В следующем примере вручную воспроизводится
функция \cmd{abs}:

\begin{code}
scalar ax = x>=0 ? x : -x
\end{code}
Конечно, в приведенном выше случае было бы намного проще написать
\texttt{ax = abs(x)}. Даайте рассмотрим следующий случай, который
использует тот факт, что тернарный оператор может быть вложенным:
\begin{code}
scalar days = (m==2) ? 28 : maxr(m.={4,6,9,11}) ? 30 : 31
\end{code}
Этот пример заслуживает нескольких комментариев. Мы хотим вычислить
количество дней в месяце, закодированных в переменной
\texttt{m}. Значение, которое мы присваиваем скалярным дням, исходит
из следующего алгоритма:

\begin{enumerate}
\item Сначала мы проверяем, является ли месяц февраль (\texttt{m==2});
  если да, то мы устанавливаем 28 дней, и все готово.\footnote{OK,
    здесь мы не учитываем високосные годы.}
\item В противном случае вычисляем матрицу нулей и единиц с помощью
  операции \verb|m.={4,6,9,11}| (примечание: для успешного
  использования оператора «dot» с целью поэлементного сравнения
  элементов смотрим раздел \ref{sec:mat-op}); если \texttt{m} равно
  любому из элементов в векторе, соответствующий элемент результата
  будет 1 (и 0 в противном случае).
\item Функция \cmd{maxr} дает максимум этого вектора, поэтому мы
  проверяем, является ли m одним из четырех 30-дневных месяцев.
\item Поскольку приведенная выше оценка является скаляром, мы помещаем
  правильное значение в \texttt{days}.
\end{enumerate}
Тернарный оператор более гибкий, чем обычный оператор \cmd{if}. С
\cmd{if}, условие \texttt{<condition>}, подлежащее оценке, всегда
должно сводиться к скаляру, но оператор тернарного запроса просто
требует, чтобы условие имело «подходящий» тип в зависимости от типов
операндов. Так, например, предположим, что у нас есть квадратная
матрица \texttt{A}, и мы хотим переключить знак отрицательных
элементов \texttt{A} на диагонали и правее ее. Вы можете использовать
операцию цикла loop \footnote{Ключевое слово \texttt{loop} подробно
  объясняется в следующем разделе.}  и написать фрагмент кода,
например, cледующего вида:

\begin{code}
matrix A = mnormal(4,4)
matrix B = A

loop r = 1 .. rows(A)
  loop c = r .. cols(A)
     if A[r,c] < 0
       B[r,c] = -A[r,c]
     endif
  endloop
endloop
\end{code}

Используя тернарный оператор, вы можете добиться того же эффекта с
помощью значительно более короткого (и быстрого) построения:
\begin{code}
matrix A = mnormal(4,4)
matrix B = upper(A.<0) ? -A : A
\end{code}

\tip{В этот момент некоторые читатели могут подумать: «Ну, это конечно
  же может быть так круто, как вы об этом рассказываете, но это
  слишком сложно для меня; я просто воспользуюсь традиционным условием
  \cmd{if}». Конечно, в этом нет ничего плохого, но в некоторых
  случаях оператор тернарного присваивания может привести к
  значительно более быстрому коду, и его использование неожиданно
  становится естественным процессом, когда к этому привыкаешь.}

\section{Циклы (loops)}
\label{sec:hr-loops}

Основная команда Hansl для зацикливания --- это конечно же \cmd{loop},
которая принимает форму:
\begin{code}
loop <control-expression> <options>
    ...
endloop
\end{code}
Другими словами, пара операторов \cmd{loop} и \cmd{endloop} включает в
себя операторы для повторения. Конечно, петли могут быть
вложенными. Поддерживаются следующие варианты
\texttt{<control-expression>} для циклов:
\begin{enumerate}
\item безусловный цикл
\item цикл while
\item индексный цикл
\item цикл foreach
\item цикл for.
\end{enumerate}
Все эти варианты описаны ниже. 

\subsection{Безусловный цикл}

Это самый простой вариант. Он принимает форму
\begin{code}
loop <times>
   ...
endloop
\end{code}
где \texttt{<times>} --- любое выражение, вычисляемое как скаляр,
т.е. необходимое количество итераций. Оно оценивается только в начале
цикла, поэтому количество итераций не может измениться внутри самого
цикла. Пример:
\begin{code}
# triangular numbers
scalar n = 6
scalar count = 1
scalar x = 0
loop n
    scalar x += count
    count++
    print x
endloop
\end{code}
приводит к
\begin{code}
              x =  1.0000000
              x =  3.0000000
              x =  6.0000000
              x =  10.000000
              x =  15.000000
              x =  21.000000
\end{code}

Обратите внимание на использование операторов приращения
(\texttt{count++}) и измененного присваивания (\texttt{x += count}).

\subsection{Индексный цикл}

Безусловный цикл используется довольно редко, так как в большинстве
случаев полезно иметь переменную счетчика (\texttt{count} (которая
посчитана в предыдущем примере). Это легко сделать с помощью
индексного цикла, синтаксис которого
\begin{code}
loop <counter>=<min>..<max>
   ...
endloop
\end{code}
Пределы \texttt{<min>} и \texttt{<max>} должны оцениваться как
скаляры; они автоматически превращаются в целые числа, если у них есть
дробная часть. Переменная \texttt{<counter>} запускается в
\texttt{<min>} и увеличивается на 1 после каждой итерации, пока она не
станет равна \texttt{<max>}.  Счетчик внутри цикла доступен только для
чтения. Вы можете получить доступ к его числовому значению через
скаляр i или использовать метод доступа \dollar{i}, который будет
выполнять подстановку строки. Внутри цикла Hansl-интерпретатор заменит
выражение \dollar{i} строковым представлением текущего значения
индексной переменной. Пример должен прояснить это: следующий ввод

\begin{code}
scalar a_1 = 57
scalar a_2 = 85
scalar a_3 = 13

loop i=1..3
    print i a_$i
endloop
\end{code}

выдает такой результат
\begin{code}
    i = 1.0000000
  a_1 = 57.000000
    i = 2.0000000
  a_2 = 85.000000
    i = 3.0000000
  a_3 = 13.000000
\end{code}

В приведенном выше примере на первой итерации значение \texttt{i}
равно 1, поэтому интерпретатор расширяет выражение от \verb|a_$i|  до
\verb|a_1|, затем обнаруживает, что скаляр с таким именем существует,
и выводит его. То же самое происходит в остальных итерациях. Если бы
один из автоматически построенных идентификаторов не был определен,
выполнение скрипта было бы прервано с ошибкой.

\subsection{Цикл \texttt{while}}

Здесь у нас есть следующее:
\begin{code}
loop while <condition>
   ...
endloop
\end{code}
где \texttt{<condition>} должно оцениваться как скаляр, который
пересчитывается на каждой итерации. Зацикливание останавливается, как
только условие \texttt{<condition>} становится ложным (0). Если
\texttt{<condition>} становится \texttt{NA}, выводится ошибка и
выполнение скрипта останавливается. По умолчанию цикл while не может
превышать 100 000 итераций, что является защитой от потенциально
бесконечных циклов. При необходимости этот параметр можно изменить,
если задать другое значение для переменной состояния
\texttt{loop\_maxiter}.
% (see chapter \ref{chap:settings}).

\subsection{Цикл \texttt{foreach}}
\label{sec:loop-foreach}
В этом случае синтаксис выглядит так:
\begin{code}
loop foreach <counter> <catalogue>
   ...
endloop
\end{code}
где \texttt{<catalogue>} может быть либо набором строк, разделенных
пробелами, либо переменной типа списка \texttt{list} (см. раздел
\ref{sec:lists}). Переменная счетчика автоматически принимает числовые
значения 1, 2, 3 и т.д. по мере выполнения сценария, но его строковое
значение (доступ к которому осуществляется путем добавления знака
доллара) затеняет названия серий в списке или в строках, которые
разделены пробелами; этот вид цикла предназначен для замены строки.
Вот пример, в котором \texttt{<catalogue>} представляет собой набор
названий функций, которые возвращают скалярное значение, если задан
скалярный аргумент:

\begin{code}
scalar x = 1
loop foreach f sqrt exp ln
    scalar y = $f(x)
    print y
endloop
\end{code}

Получаются следующие значения:
\begin{code}
              y =  1.0000000
              y =  2.7182818
              y =  0.0000000
\end{code}

\subsection{Цикл \texttt{for}}

Заключительная форма управления циклом эмулирует оператор for на языке
C. Команда loop \cmd{for}, за которой следуют три компонентных
выражения, разделенных точкой с запятой и заключенных в круглые
скобки, то есть
\begin{code}
loop for (<init>; <cont>; <modifier>)
   ...
endloop
\end{code}
Эти три компонента следующие:
\begin{enumerate}
\item Инициализация (\texttt{<init>}): должен использоваться оператор
  присваивания, вычисляемый в начале цикла.
\item Условие продолжения (\texttt{<cont>}): оно оценивается в начале
  каждой итерации (включая начальный цикл). Если выражение оценивается
  как истинное (ненулевое), итерация продолжается, в противном случае
  она останавливается.
\item Модификатор (\texttt{<modifier>}): выражение, изменяющее
  значение некоторой переменной. Оно оценивается до проверки условия
  продолжения на каждой последующей итерации (после первой).
\end{enumerate}

Вот пример, в котором мы находим квадратный корень из числа путем
последовательных приближений, используя метод Ньютона:
\begin{code}
# find the square root of x iteratively via Newton's method
scalar x = 256
d = 1
loop for (y=(x+1)/2; abs(d) > 1.0e-7; y -= d/(2*y))
    d = y*y - x
    printf "y = %15.10f, d = %g\n", y, d
endloop

printf "sqrt(%g) = %g\n", x, y
\end{code}
Если решить эту задачу, получается:
\begin{code}
y =  128.5000000000, d = 16256.3
y =   65.2461089494, d = 4001.05
y =   34.5848572866, d = 940.112
y =   20.9934703720, d = 184.726
y =   16.5938690915, d = 19.3565
y =   16.0106268314, d = 0.340172
y =   16.0000035267, d = 0.000112855
y =   16.0000000000, d = 1.23919e-11

Number of iterations: 8

sqrt(256) = 16
\end{code}
Помните об ограниченной точности арифметики с плавающей
точкой. Например, фрагмент кода ниже будет повторяться бесконечно на
большинстве платформ, потому что \texttt{x} никогда не будет равняться
точно 0,01, даже если кажется, что так и должно быть.
\begin{code}
loop for (x=1; x!=0.01; x=x*0.1)
    printf "x = .18g\n", x
endloop  
\end{code}
Однако, если вы замените условие \texttt{x!=0.01} на \texttt{x>=0.01},
код будет работать так, как (вероятно) вам и хотелось.
 
\subsection{Варианты циклов}

Оператору цикла можно сформулировать задание двумя способами. Один из
них – многословный \option{verbose}. Здесь пачатаются дополнительные
выходные данные для отслеживания хода выполнения цикла; это не имеет
никакого другого значения, и семантика цикла остается без изменений.
Второй способ через параметр \option{progressive}, который в основном
используется как быстрый и эффективный способ настройки имитационных
исследований. Когда задана эта опция, нескольким командам (а именно
\cmd{print} и \cmd{store}) присваиваются специальные
значения. Пожалуйста, обратитесь к Руководству пользователя Gretl для
получения дополнительной информации.
 
\subsection{Выход из цикла}
\label{sec:loop-break}

Команда\cmd{break} позволяет при необходимости прервать
цикл.\footnote{Hansl не предоставляет эквивалента инструкции
  \texttt{continue} на языке C.}  Обратите внимание, что если вы
сделаете циклы вложенными, команда \cmd{break} в самом внутреннем
цикле прервет только этот цикл, а не все остальные (внешние). Ниже мы
приводим пример, в котором используется цикл \texttt{while} для
вычисления квадратного корня аналогично приведенному выше примеру, и
команда \cmd{break} для выхода из цикла, когда работа выполнена.
\begin{code}
scalar x = 256
scalar y = 1
loop while 1
    d = y*y - x
    if abs(d) < 1.0e-7
        break
    else
        y -= d/(2*y)
        printf "y = %15.10f, d = %g\n", y, d
    endif
endloop

printf "sqrt(%g) = %g\n", x, y
\end{code}

\section{Модификатор \cmd{catch}}

Hansl предлагает элементарную форму обработки исключений с помощью
ключевого слова \cmd{catch}. Это не команда сама по себе, но ее можно
использовать в качестве префикса к большинству обычных команд: эффект
заключается в том, чтобы предотвратить завершение сценария, если при
выполнении команды произошла ошибка. Если ошибка все же возникает, она
регистрируется во внутреннем коде ошибки, к которому можно получить
доступ через \dollar{error} (нулевое значение, указывающее на
успех). Значение \dollar{error} всегда следует проверять сразу после
использования \cmd{catch}, и предпринять соответствующие действия в
том случае, если команда не удалась. Вот простой пример:

\begin{code}
matrix a = floor(2*muniform(2,2))
catch ai = inv(a)
scalar err = $error
if err
    printf "The matrix\n%6.0f\nis singular!\n", a
else
    print ai
endif
\end{code}

Обратите внимание, что ключевое приставку «\cmd{catch}» нельзя
использовать перед \cmd{if}, \cmd{elif} или \cmd{endif}. Кроме того,
она не используется при вызовах пользовательских функций; а
предназначена для использования только с командами gretl и вызовами
«встроенных» функции или операторов. Предположим, вы пишете пакет
функций, который включает в себя вспомогательные функции, которые
могут выйти из строя при определенных условиях, и вы хотите
предотвратить прерывание выполнения программы из-за подобных ошибок. В
этом случае мы рекомендуем использовать \cmd{catch} внутри конкретной
функции, и если условие ошибки обнаружено, сообщить об этом через
caller, который возвратит соответствующее «недопустимое» значение ---
скажем, \texttt{NA} (для функции, возвращающей скаляр) или пустую
матрицу. Например:

\begin{code}
function scalar may_fail (matrix *m)
  catch scalar x = ... # вызвать встроенную функцию
  if $error
    x = NA
  endif
  return x
end function

function scalar caller (...)
  matrix m = ... # что-нибудь
  scalar x = may_fail(&m)
  if na(x)
    print "Couldn't calculate x"
  else
    printf "Calculated x = %g\n", x
  endif
end function
\end{code}

Чего здесь делать не следует, так это применять catch к
\cmd{may\_fail()}

\begin{code}
function scalar caller (...)
  matrix m = ... # что-нибудь
  catch scalar x = may_fail(&m) # нельзя!
  ...
end function
\end{code}

поскольку это может ввести gretl в замешательство.

\section{Завершение сценария с помощью \cmd{quit}}

Когда в сценарии Hansl встречается команда \cmd{quit}, его выполнение
останавливается. Если программа \app{gretlcli} в командной строке
работает в режиме пакетной обработки (batch mode), управление
сценарием отдается операционной системе; если же gretl запущена в
интерактивном режиме, она будет ждать интерактивного ввода.  Команда
\cmd{quit} редко используется в сценариях, поскольку выполнение
автоматически останавливается при завершении сценария, но ее можно
использовать вместе \cmd{catch}. Автор скрипта может устроить так, что
при обнаружении определенного состояния ошибки выводится
соответствующее сообщение и выполнение сценария
приостанавливается. Еще одно применение \cmd{quit} может быть полезным
при разработке программ: если вы хотите проверить выполнение начальной
части сложного скрипта, наиболее удобным решением может быть временное
вложение команды \cmd{quit} в подходящую строку сценария.

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