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