1\chapter{Поток управления}
2\label{chap:hp-ctrlflow}
3
4Основными средствами управления потоком управления на языке Hansl
5являются оператор \cmd{if} (условное выполнение), оператор \cmd{loop}
6(повторное выполнение), модификатор \cmd{catch} (который включает
7перехват ошибок, которые в противном случае остановили бы выполнение
8скрипта), и команда \cmd{quit} (которая принудительно завершает
9работу).
10
11\section{Оператор \cmd{if}}
12
13Условное выполнение в Hansl использует ключевое слово \cmd{if}. Его
14наиболее полное использование выглядит следующим образом:
15\begin{code}
16if <condition>
17   ...
18elif <condition>
19   ...
20else
21   ...
22endif
23\end{code}
24
25Важно отметить:
26\begin{itemize}
27
28\item условие \texttt{<condition>} может быть любым выражением,
29  вычисляющим скаляр: 0 интерпретируется как «ложь», ненулевое
30  значение интерпретируется как «истина»; \texttt{NA} генерирует
31  ошибку.
32\item После \cmd{if} «then» явно не указывается; не существует
33  ключевого слова \texttt{then}, как, например, в Pascal или Basic.
34\item Строки \cmd{elif} и \cmd{else} необязательны: минимальная форма
35  --- это просто \texttt{if} \dots{} \texttt{endif}.
36\item Условные блоки такого типа могут быть вложены до максимальной
37  глубины 1024.
38\end{itemize}
39
40Например:
41\begin{code}
42scalar x = 15
43
44# --- simple if ----------------------------------
45if x >= 100
46   printf "%g is more than two digits long\n", x
47endif
48
49# --- if with else -------------------------------
50if x >= 0
51   printf "%g is non-negative\n", x
52else
53   printf "%g is negative\n", x
54endif
55
56# --- multiple branches --------------------------
57if missing(x)
58   printf "%g is missing\n", x
59elif x < 0
60   printf "%g is negative\n", x
61elif floor(x) == x
62   printf "%g is an integer\n", x
63else
64   printf "%g is a positive number with a fractional part\n", x
65endif
66\end{code}
67
68В приведенном выше примере ключевое слово \cmd{elif} можно повторять,
69в результате чего оператор \cmd{if} становится оператором
70многостороннего перехода. В Hansl нет отдельного оператора
71\cmd{switch} или \cmd{case}. С одним или более \cmd{elif}, Hansl
72выполнит первый, для которого выполнено логическое условие, а затем
73перейти к \cmd{endif}.
74
75\tip{Пользователи Stata, будьте осторожны: оператор команды \cmd{if} в
76  Hansl в корне отличается от варианта \texttt{if} в Stata: в
77  последнем случае выбирается подвыборка наблюдений для некоторого
78  действия, а в первом используется для определения того, должна ли
79  серия условий выполняться или нет. «\cmd{If}» в Hansl это то, что
80  Stata называет “branching \texttt{if}”.}
81
82
83\subsection{Оператор тернарного запроса}
84
85Помимо оператора \texttt{if} можно использовать оператор тернарного
86запроса \texttt{?:} для выполнения условного присваивания на «микро»
87уровне. Это выглядит так:
88\begin{code}
89result = <condition> ? <true-value> : <false-value>
90\end{code}
91Если \texttt{<condition>} оценивается как истинное (ненулевое), то
92\texttt{<true-value>} присваивается результату, в противном случае
93результат \texttt{result} будет содержать
94\texttt{<false-value>}. \footnote{Некоторым читателям может быть
95  интересно узнать, что оператор условного присваивания работает точно
96  так же, как функция "если" \texttt{=IF()} в электронных таблицах.}
97Это явно более компактно, чем \texttt{if} \dots{} \texttt{else}
98\dots{} \texttt{endif}. В следующем примере вручную воспроизводится
99функция \cmd{abs}:
100
101\begin{code}
102scalar ax = x>=0 ? x : -x
103\end{code}
104Конечно, в приведенном выше случае было бы намного проще написать
105\texttt{ax = abs(x)}. Даайте рассмотрим следующий случай, который
106использует тот факт, что тернарный оператор может быть вложенным:
107\begin{code}
108scalar days = (m==2) ? 28 : maxr(m.={4,6,9,11}) ? 30 : 31
109\end{code}
110Этот пример заслуживает нескольких комментариев. Мы хотим вычислить
111количество дней в месяце, закодированных в переменной
112\texttt{m}. Значение, которое мы присваиваем скалярным дням, исходит
113из следующего алгоритма:
114
115\begin{enumerate}
116\item Сначала мы проверяем, является ли месяц февраль (\texttt{m==2});
117  если да, то мы устанавливаем 28 дней, и все готово.\footnote{OK,
118    здесь мы не учитываем високосные годы.}
119\item В противном случае вычисляем матрицу нулей и единиц с помощью
120  операции \verb|m.={4,6,9,11}| (примечание: для успешного
121  использования оператора «dot» с целью поэлементного сравнения
122  элементов смотрим раздел \ref{sec:mat-op}); если \texttt{m} равно
123  любому из элементов в векторе, соответствующий элемент результата
124  будет 1 (и 0 в противном случае).
125\item Функция \cmd{maxr} дает максимум этого вектора, поэтому мы
126  проверяем, является ли m одним из четырех 30-дневных месяцев.
127\item Поскольку приведенная выше оценка является скаляром, мы помещаем
128  правильное значение в \texttt{days}.
129\end{enumerate}
130Тернарный оператор более гибкий, чем обычный оператор \cmd{if}. С
131\cmd{if}, условие \texttt{<condition>}, подлежащее оценке, всегда
132должно сводиться к скаляру, но оператор тернарного запроса просто
133требует, чтобы условие имело «подходящий» тип в зависимости от типов
134операндов. Так, например, предположим, что у нас есть квадратная
135матрица \texttt{A}, и мы хотим переключить знак отрицательных
136элементов \texttt{A} на диагонали и правее ее. Вы можете использовать
137операцию цикла loop \footnote{Ключевое слово \texttt{loop} подробно
138  объясняется в следующем разделе.}  и написать фрагмент кода,
139например, cледующего вида:
140
141\begin{code}
142matrix A = mnormal(4,4)
143matrix B = A
144
145loop r = 1 .. rows(A)
146  loop c = r .. cols(A)
147     if A[r,c] < 0
148       B[r,c] = -A[r,c]
149     endif
150  endloop
151endloop
152\end{code}
153
154Используя тернарный оператор, вы можете добиться того же эффекта с
155помощью значительно более короткого (и быстрого) построения:
156\begin{code}
157matrix A = mnormal(4,4)
158matrix B = upper(A.<0) ? -A : A
159\end{code}
160
161\tip{В этот момент некоторые читатели могут подумать: «Ну, это конечно
162  же может быть так круто, как вы об этом рассказываете, но это
163  слишком сложно для меня; я просто воспользуюсь традиционным условием
164  \cmd{if}». Конечно, в этом нет ничего плохого, но в некоторых
165  случаях оператор тернарного присваивания может привести к
166  значительно более быстрому коду, и его использование неожиданно
167  становится естественным процессом, когда к этому привыкаешь.}
168
169\section{Циклы (loops)}
170\label{sec:hr-loops}
171
172Основная команда Hansl для зацикливания --- это конечно же \cmd{loop},
173которая принимает форму:
174\begin{code}
175loop <control-expression> <options>
176    ...
177endloop
178\end{code}
179Другими словами, пара операторов \cmd{loop} и \cmd{endloop} включает в
180себя операторы для повторения. Конечно, петли могут быть
181вложенными. Поддерживаются следующие варианты
182\texttt{<control-expression>} для циклов:
183\begin{enumerate}
184\item безусловный цикл
185\item цикл while
186\item индексный цикл
187\item цикл foreach
188\item цикл for.
189\end{enumerate}
190Все эти варианты описаны ниже.
191
192\subsection{Безусловный цикл}
193
194Это самый простой вариант. Он принимает форму
195\begin{code}
196loop <times>
197   ...
198endloop
199\end{code}
200где \texttt{<times>} --- любое выражение, вычисляемое как скаляр,
201т.е. необходимое количество итераций. Оно оценивается только в начале
202цикла, поэтому количество итераций не может измениться внутри самого
203цикла. Пример:
204\begin{code}
205# triangular numbers
206scalar n = 6
207scalar count = 1
208scalar x = 0
209loop n
210    scalar x += count
211    count++
212    print x
213endloop
214\end{code}
215приводит к
216\begin{code}
217              x =  1.0000000
218              x =  3.0000000
219              x =  6.0000000
220              x =  10.000000
221              x =  15.000000
222              x =  21.000000
223\end{code}
224
225Обратите внимание на использование операторов приращения
226(\texttt{count++}) и измененного присваивания (\texttt{x += count}).
227
228\subsection{Индексный цикл}
229
230Безусловный цикл используется довольно редко, так как в большинстве
231случаев полезно иметь переменную счетчика (\texttt{count} (которая
232посчитана в предыдущем примере). Это легко сделать с помощью
233индексного цикла, синтаксис которого
234\begin{code}
235loop <counter>=<min>..<max>
236   ...
237endloop
238\end{code}
239Пределы \texttt{<min>} и \texttt{<max>} должны оцениваться как
240скаляры; они автоматически превращаются в целые числа, если у них есть
241дробная часть. Переменная \texttt{<counter>} запускается в
242\texttt{<min>} и увеличивается на 1 после каждой итерации, пока она не
243станет равна \texttt{<max>}.  Счетчик внутри цикла доступен только для
244чтения. Вы можете получить доступ к его числовому значению через
245скаляр i или использовать метод доступа \dollar{i}, который будет
246выполнять подстановку строки. Внутри цикла Hansl-интерпретатор заменит
247выражение \dollar{i} строковым представлением текущего значения
248индексной переменной. Пример должен прояснить это: следующий ввод
249
250\begin{code}
251scalar a_1 = 57
252scalar a_2 = 85
253scalar a_3 = 13
254
255loop i=1..3
256    print i a_$i
257endloop
258\end{code}
259
260выдает такой результат
261\begin{code}
262    i = 1.0000000
263  a_1 = 57.000000
264    i = 2.0000000
265  a_2 = 85.000000
266    i = 3.0000000
267  a_3 = 13.000000
268\end{code}
269
270В приведенном выше примере на первой итерации значение \texttt{i}
271равно 1, поэтому интерпретатор расширяет выражение от \verb|a_$i|  до
272\verb|a_1|, затем обнаруживает, что скаляр с таким именем существует,
273и выводит его. То же самое происходит в остальных итерациях. Если бы
274один из автоматически построенных идентификаторов не был определен,
275выполнение скрипта было бы прервано с ошибкой.
276
277\subsection{Цикл \texttt{while}}
278
279Здесь у нас есть следующее:
280\begin{code}
281loop while <condition>
282   ...
283endloop
284\end{code}
285где \texttt{<condition>} должно оцениваться как скаляр, который
286пересчитывается на каждой итерации. Зацикливание останавливается, как
287только условие \texttt{<condition>} становится ложным (0). Если
288\texttt{<condition>} становится \texttt{NA}, выводится ошибка и
289выполнение скрипта останавливается. По умолчанию цикл while не может
290превышать 100 000 итераций, что является защитой от потенциально
291бесконечных циклов. При необходимости этот параметр можно изменить,
292если задать другое значение для переменной состояния
293\texttt{loop\_maxiter}.
294% (see chapter \ref{chap:settings}).
295
296\subsection{Цикл \texttt{foreach}}
297\label{sec:loop-foreach}
298В этом случае синтаксис выглядит так:
299\begin{code}
300loop foreach <counter> <catalogue>
301   ...
302endloop
303\end{code}
304где \texttt{<catalogue>} может быть либо набором строк, разделенных
305пробелами, либо переменной типа списка \texttt{list} (см. раздел
306\ref{sec:lists}). Переменная счетчика автоматически принимает числовые
307значения 1, 2, 3 и т.д. по мере выполнения сценария, но его строковое
308значение (доступ к которому осуществляется путем добавления знака
309доллара) затеняет названия серий в списке или в строках, которые
310разделены пробелами; этот вид цикла предназначен для замены строки.
311Вот пример, в котором \texttt{<catalogue>} представляет собой набор
312названий функций, которые возвращают скалярное значение, если задан
313скалярный аргумент:
314
315\begin{code}
316scalar x = 1
317loop foreach f sqrt exp ln
318    scalar y = $f(x)
319    print y
320endloop
321\end{code}
322
323Получаются следующие значения:
324\begin{code}
325              y =  1.0000000
326              y =  2.7182818
327              y =  0.0000000
328\end{code}
329
330\subsection{Цикл \texttt{for}}
331
332Заключительная форма управления циклом эмулирует оператор for на языке
333C. Команда loop \cmd{for}, за которой следуют три компонентных
334выражения, разделенных точкой с запятой и заключенных в круглые
335скобки, то есть
336\begin{code}
337loop for (<init>; <cont>; <modifier>)
338   ...
339endloop
340\end{code}
341Эти три компонента следующие:
342\begin{enumerate}
343\item Инициализация (\texttt{<init>}): должен использоваться оператор
344  присваивания, вычисляемый в начале цикла.
345\item Условие продолжения (\texttt{<cont>}): оно оценивается в начале
346  каждой итерации (включая начальный цикл). Если выражение оценивается
347  как истинное (ненулевое), итерация продолжается, в противном случае
348  она останавливается.
349\item Модификатор (\texttt{<modifier>}): выражение, изменяющее
350  значение некоторой переменной. Оно оценивается до проверки условия
351  продолжения на каждой последующей итерации (после первой).
352\end{enumerate}
353
354Вот пример, в котором мы находим квадратный корень из числа путем
355последовательных приближений, используя метод Ньютона:
356\begin{code}
357# find the square root of x iteratively via Newton's method
358scalar x = 256
359d = 1
360loop for (y=(x+1)/2; abs(d) > 1.0e-7; y -= d/(2*y))
361    d = y*y - x
362    printf "y = %15.10f, d = %g\n", y, d
363endloop
364
365printf "sqrt(%g) = %g\n", x, y
366\end{code}
367Если решить эту задачу, получается:
368\begin{code}
369y =  128.5000000000, d = 16256.3
370y =   65.2461089494, d = 4001.05
371y =   34.5848572866, d = 940.112
372y =   20.9934703720, d = 184.726
373y =   16.5938690915, d = 19.3565
374y =   16.0106268314, d = 0.340172
375y =   16.0000035267, d = 0.000112855
376y =   16.0000000000, d = 1.23919e-11
377
378Number of iterations: 8
379
380sqrt(256) = 16
381\end{code}
382Помните об ограниченной точности арифметики с плавающей
383точкой. Например, фрагмент кода ниже будет повторяться бесконечно на
384большинстве платформ, потому что \texttt{x} никогда не будет равняться
385точно 0,01, даже если кажется, что так и должно быть.
386\begin{code}
387loop for (x=1; x!=0.01; x=x*0.1)
388    printf "x = .18g\n", x
389endloop
390\end{code}
391Однако, если вы замените условие \texttt{x!=0.01} на \texttt{x>=0.01},
392код будет работать так, как (вероятно) вам и хотелось.
393
394\subsection{Варианты циклов}
395
396Оператору цикла можно сформулировать задание двумя способами. Один из
397них – многословный \option{verbose}. Здесь пачатаются дополнительные
398выходные данные для отслеживания хода выполнения цикла; это не имеет
399никакого другого значения, и семантика цикла остается без изменений.
400Второй способ через параметр \option{progressive}, который в основном
401используется как быстрый и эффективный способ настройки имитационных
402исследований. Когда задана эта опция, нескольким командам (а именно
403\cmd{print} и \cmd{store}) присваиваются специальные
404значения. Пожалуйста, обратитесь к Руководству пользователя Gretl для
405получения дополнительной информации.
406
407\subsection{Выход из цикла}
408\label{sec:loop-break}
409
410Команда\cmd{break} позволяет при необходимости прервать
411цикл.\footnote{Hansl не предоставляет эквивалента инструкции
412  \texttt{continue} на языке C.}  Обратите внимание, что если вы
413сделаете циклы вложенными, команда \cmd{break} в самом внутреннем
414цикле прервет только этот цикл, а не все остальные (внешние). Ниже мы
415приводим пример, в котором используется цикл \texttt{while} для
416вычисления квадратного корня аналогично приведенному выше примеру, и
417команда \cmd{break} для выхода из цикла, когда работа выполнена.
418\begin{code}
419scalar x = 256
420scalar y = 1
421loop while 1
422    d = y*y - x
423    if abs(d) < 1.0e-7
424        break
425    else
426        y -= d/(2*y)
427        printf "y = %15.10f, d = %g\n", y, d
428    endif
429endloop
430
431printf "sqrt(%g) = %g\n", x, y
432\end{code}
433
434\section{Модификатор \cmd{catch}}
435
436Hansl предлагает элементарную форму обработки исключений с помощью
437ключевого слова \cmd{catch}. Это не команда сама по себе, но ее можно
438использовать в качестве префикса к большинству обычных команд: эффект
439заключается в том, чтобы предотвратить завершение сценария, если при
440выполнении команды произошла ошибка. Если ошибка все же возникает, она
441регистрируется во внутреннем коде ошибки, к которому можно получить
442доступ через \dollar{error} (нулевое значение, указывающее на
443успех). Значение \dollar{error} всегда следует проверять сразу после
444использования \cmd{catch}, и предпринять соответствующие действия в
445том случае, если команда не удалась. Вот простой пример:
446
447\begin{code}
448matrix a = floor(2*muniform(2,2))
449catch ai = inv(a)
450scalar err = $error
451if err
452    printf "The matrix\n%6.0f\nis singular!\n", a
453else
454    print ai
455endif
456\end{code}
457
458Обратите внимание, что ключевое приставку «\cmd{catch}» нельзя
459использовать перед \cmd{if}, \cmd{elif} или \cmd{endif}. Кроме того,
460она не используется при вызовах пользовательских функций; а
461предназначена для использования только с командами gretl и вызовами
462«встроенных» функции или операторов. Предположим, вы пишете пакет
463функций, который включает в себя вспомогательные функции, которые
464могут выйти из строя при определенных условиях, и вы хотите
465предотвратить прерывание выполнения программы из-за подобных ошибок. В
466этом случае мы рекомендуем использовать \cmd{catch} внутри конкретной
467функции, и если условие ошибки обнаружено, сообщить об этом через
468caller, который возвратит соответствующее «недопустимое» значение ---
469скажем, \texttt{NA} (для функции, возвращающей скаляр) или пустую
470матрицу. Например:
471
472\begin{code}
473function scalar may_fail (matrix *m)
474  catch scalar x = ... # вызвать встроенную функцию
475  if $error
476    x = NA
477  endif
478  return x
479end function
480
481function scalar caller (...)
482  matrix m = ... # что-нибудь
483  scalar x = may_fail(&m)
484  if na(x)
485    print "Couldn't calculate x"
486  else
487    printf "Calculated x = %g\n", x
488  endif
489end function
490\end{code}
491
492Чего здесь делать не следует, так это применять catch к
493\cmd{may\_fail()}
494
495\begin{code}
496function scalar caller (...)
497  matrix m = ... # что-нибудь
498  catch scalar x = may_fail(&m) # нельзя!
499  ...
500end function
501\end{code}
502
503поскольку это может ввести gretl в замешательство.
504
505\section{Завершение сценария с помощью \cmd{quit}}
506
507Когда в сценарии Hansl встречается команда \cmd{quit}, его выполнение
508останавливается. Если программа \app{gretlcli} в командной строке
509работает в режиме пакетной обработки (batch mode), управление
510сценарием отдается операционной системе; если же gretl запущена в
511интерактивном режиме, она будет ждать интерактивного ввода.  Команда
512\cmd{quit} редко используется в сценариях, поскольку выполнение
513автоматически останавливается при завершении сценария, но ее можно
514использовать вместе \cmd{catch}. Автор скрипта может устроить так, что
515при обнаружении определенного состояния ошибки выводится
516соответствующее сообщение и выполнение сценария
517приостанавливается. Еще одно применение \cmd{quit} может быть полезным
518при разработке программ: если вы хотите проверить выполнение начальной
519части сложного скрипта, наиболее удобным решением может быть временное
520вложение команды \cmd{quit} в подходящую строку сценария.
521
522%%% Local Variables:
523%%% mode: latex
524%%% TeX-master: "hansl-primer"
525%%% End:
526