Программирование на Athanor – 13 (запросы к среде выполнения и другие отладочные инструменты)
Дополнительные средства отладки и виды деклараций
Напомним тривиальный факт (с которого и начался этот цикл статей): Атанор – это интерпретируемый язык программирования. Не надо говорить, что в этом есть как свои плюсы, так и свои минусы. К очевидным минусам – можно отнести то, что выполнение программного кода обычно будет медленнее, чем в компилируемых языках. А вот к плюсам – не только то, что на языке просто реализовать некоторые концепции, практически недоступные в компилируемых языках, но и то, что непосредственно в среде выполнения доступно много полезных инструментов для проверок и отладки. Некоторые из них мы здесь рассмотрим.
Прежде всего есть средства для вывода листинга доступных переменных и функторов:
|
x_vars (Scope)
|
Выводит список всех переменных в области видимости Scope (по умолчанию, глобальной)
|
|
x_funcs (Scope)
|
Выводит список всех функторов в области видимости Scope (по умолчанию, глобальной)
|
Первая из этих операций, x_vars, уже упоминалась ранее в статье 3 (правда, в варианте без операнда). Операнд Scope является необязательным: без него неявно предполагается глобальная область видимости. Все явно не декларированные переменные – считаются глобальными. (Все функторы, декларации которых никуда не вложены синтаксически – тоже.)
Допустим, мы уже определили несколько глобальных переменных (со значениями разных типов):
- ivar = 23 * 56;
- fvar = sqr (sin (1));
- svar = "Goodbye!";
- lvar = [1 'alpha' 2 'beta' 3 'gamma'];
Если после этого выполнить x_vars () – получим такой список:
реклама
ivar = 1288; fvar = 0.91731728; svar = "Goodbye!"; lvar = (1, "alpha", 2, "beta", 3, "gamma");
Здесь нет никаких сюрпризов: мы узнали, какие у нас доступны переменные, и какие им присвоены значения. Если переменная содержит () – то в списке выводится только её имя, для компактности. (Переменная без значения – легко может быть создана даже случайно. Как уже пояснялось раньше: если на переменную существовала хотя бы одна ссылка в любом выражении – она автоматически материализуется! Однако, если значение ей не было присвоено – она будет содержать пустоту.) Формат вывода для скалярных значений и для списков – обычно совпадает с исходным форматом, но он всегда стандартный. (Например, списки всегда выводятся в формате с круглыми скобками, и запятыми в качестве разделителей элементов, и вложенные списковые элементы – тоже.) Как и большинство функторов, x_vars тоже возвращает значение: целое число, равное количеству выведенных переменных (в данном случае, 4).
Определим теперь несколько простых функторов:
- ! vector_len (X Y Z) = sqr (X*X + Y*Y + Z*Z);
- ! list_product (list) : [Item Prod] = { Prod = 1; l_loop (Item, list, Prod =*: Item); Prod };
- ! embrace (list) = '{' +$ s_join (', ', list) +$ '}';
Если после этого выполнить x_funcs () – получим такой результат:
! vector_len(X Y Z) [3] = sqr^add^(add^(mul^(X, X), mul^(Y, Y)), mul^(Z, Z));
! list_product(list) [3] = {
set^(Prod, 1);
l_loop^(Item, list, comb^mul^(Prod, Item));
Prod };
! embrace(list) [1] = s_cat^(s_cat^('{', s_join^(", ", list)), '}');
Здесь все определённые нами функторы – с именами, списками параметров, и выражениями, которые являются их реализациями. Заметим, что между списком параметров функтора и его реализацией – выводится целое число в квадратных скобках. Это - размер локального контекста для данного функтора (он обычно равен сумме количества его параметров и его локальных переменных). Также заметим, что все выражения также выводятся в стандартном внутреннем формате (по большей части, в виде структуры вложенных вызовов разных функторов). (Практически, именно так, как они выводятся - они и хранятся в оперативной памяти при работе программы.) Для самых наблюдательных ещё заметим, что все совмещённые присваивания (типа "Prod =*: Item") - транслируются именно так, в выражения с вызовом системного функтора comb. На самом деле, его можно вызвать и напрямую: comb (Prod * Item) – не только вычисляет произведение двух операндов, но и пытается присвоить результат первому операнду (для этого, разумеется, он должен быть мутабельным). Наконец, как и x_vars, вызов x_funcs возвращает значение: целое число, равное количеству выведенных функторов (в данном случае, 3). (Как и x_vars, так и x_funcs выводят листинги в поток диагностики (f_err ()) – это надо иметь в виду при перенаправлении вывода в файл.)
Однако, у обоих функторов может быть и параметр: это ссылка на произвольный определённый пользователем функтор. Если мы выполним следующее:
- x_vars (! vector_len);
- x_vars (! list_product);
- x_vars (! embrace);
то в качестве результата, увидим три списка: локальные параметры/переменные данных функторов.
реклама
X; Y; Z; list; Item; Prod; list;
На самом деле, и параметры, и прочие локальные переменные выводятся единым списком. (Единственная разница между ними – параметрам при вызове передаются значения, а локальные переменные просто очищаются.) Заметим, что в списке у них нет значений: это означает, что они все равны (). Этого и следует ожидать: в отличие от глобальных переменных, все локальные переменные функтора являются частью его локального контекста, который создаётся только при его вызове. Если функтор не вызван, его локального контекста не существует – поэтому, все его переменные равны ().
Однако, если вызвать x_vars (! func) во время вызова самого func – можно увидеть значения параметров и локальных переменных. Попробуем следующее:
! show_locals (text count) : [result] = {
result = ('{' +$ text +$ '}') *$ count;
x_vars (! show_locals);
result };
show_locals ("ABCD", 2);
show_locals ("LMN", 3);
show_locals ("XYZ", 4);
Напомним, что имя определяемого функтора уже доступно в его теле (так что мы легко можем передать его как параметр для x_vars). В результате, увидим и параметры, и локальные переменные:
text = "ABCD";
count = 2;
result = "{ABCD}{ABCD}";
text = "LMN";
count = 3;
result = "{LMN}{LMN}{LMN}";
text = "XYZ";
count = 4;
result = "{XYZ}{XYZ}{XYZ}{XYZ}";
Если функтор был вызван рекурсивно, то значения его параметров/локальных переменных берутся из самого актуального (т.е. относящиеся к самому "глубокому" из вызовов) контекста. Параметр в виде ссылки на функтор также можно передать и x_funcs – и тогда будет выведен полный список функторов, определённых внутри данного функтора. Да, пользовательские определения функторов вполне могут быть вложенными (причём, на неограниченную глубину)! Однако, тема эта сама по себе достаточно интересна для отдельной статьи. Но в примерах из этой статьи все функторы определены глобально, так что в x_funcs с параметром нет необходимости.
Нетрудно узнать значение не только переменной, но и произвольного выражения:
|
x_dump (Expr)
|
Выводит значение выражения Expr
|
Значение выражения Expr также выводится в своей "отладочной" форме (в той же самой, в какой его выводят x_vars и x_funcs). Разницу между этим форматом и тем, в котором происходит обычный вывод в поток, необходимо понимать. Заметим, что финальный перевод строки x_dump не добавляет (если он нужен, его требуется вывести в f_err () самостоятельно).
x_dump ();
f_err () <: "\n";
x_dump (7 * 11 * 13);
f_err () <: "\n";
x_dump (sin (1) * cos (1));
f_err () <: "\n";
x_dump ("Working from ", 7, " to ", 11);
f_err () <: "\n";
На выходе получим следующее:
()
1001
0.45464871
("Working from ", 7, " to ", 11)
В целом, как и следовало ожидать: если мы передадим x_dump один (скалярный) операнд – он и выводится как скаляр; если мы передаём список из нескольких операндов – они выводятся как список; если мы ничего не передаём – выводится ().
Иногда автоматическое создание переменных нежелательно. При интерактивной работе с интерпретатором оно очень удобно, но в достаточно сложной и большой программе это существенно увеличивает риск ошибок. А чтобы их избежать, желательно явно декларировать все переменные (в том числе, и глобальные). И для этого есть специальный декларатор:
реклама
:[var_A var_B var_C ... var_X var_Y var_Z]
Здесь явным образом декларируются все перечисленные переменные: от var_A до var_Z. Помимо этого, подобная декларация включает режим явного декларирования переменных для всего текущего файла (или текущей сессии интерпретатора, если он работает интерактивно). И, после неё, никакого автоматического декларирования для переменных (даже глобальных) не будет: каждая используемая переменная должна быть объявлена явно! Далее таких деклараций может быть сколько угодно (и любые новые переменные, которые потребуются для работы, можно объявить в любой момент) – но объявлять их теперь надо, причём до первого использования. В одной декларации можно объявить сколько угодно переменных – или, напротив, ни одной:
:[]
Хотя эта декларация пустая, она тоже законная, и имеет тот же (единственный) побочный эффект: включение режима явного декларирования переменных! Это почти идиома (примерно, как директива "use strict" в Perl): после неё, все дальнейшие переменные должны декларироваться, так что типичные ошибки (вроде неправильных имён переменных) вылавливаются немедленно! Поэтому для серьёзных программ – этот режим безусловно рекомендуется.
Декларировать можно не только глобальные переменные, но и глобальные константы. Синтаксис их описания тоже довольно сходный:
:(const_A = value_A, const_B = value_B ... const_Z = value_Z)
В отличие от переменных, константам при декларировании немедленно присваивается некое значение-инициализатор, и изменить его потом нельзя. Значение это может быть любого типа (допустимы и скалярные значения, и списки, и более сложные типы данных). Заметим, что имена декларируемых констант не пересекаются с именами переменных: они всегда относятся к пространству имён функторов! Поэтому, чтобы вывести объявленные константы, надо выполнить не x_vars (), а x_funcs (). По этой же причине, имена констант должны отличаться от имён функторов, декларированных (раньше или позже) в той же области видимости. Собственно, константы тоже являются функторами (хотя и тривиальными): у них нет параметров, а значение они всегда возвращают только одно. Однако, как и на функторы, на них вполне можно получить косвенную ссылку:
ref = ! const_A
Её потом можно даже вызвать обычным способом:
ref ! ()
– получив в результате значение const_A. Иногда такие приёмы оказываются полезны. Кстати, при косвенном вызове можно даже передать ref любые аргументы (допустимо, но не имеет реального смысла: константа всё равно их игнорирует).
Значения, присвоенные константам, не обязаны быть литералами. Однако, они фиксируются на момент их вычисления: значения констант будут именно такими, которыми были значения соответствующих выражений при декларировании констант. Впрочем, если константе присваивается мутабельная структура данных (например, список), то уже требуется некоторая осторожность. Хотя константы (сами по себе) иммутабельны, но их "константность" не защищает от изменений список-значение: его элементам потом могут быть присвоены новые значения, возможны даже вставки/удаления элементов и т.п. Другое дело, что менять константу обычно не рекомендуется (хотя возможны ситуации, когда некая сложная структура данных сперва декларируется как константная, а потом уже её элементам присваиваются какие-то значения). Наконец, заметим, что декларирование константы (даже единственной) – также обязательно и немедленно включает механизм явного декларирования переменных! И после неё, обращение к любой переменной, которая не была объявлена явным образом – также будет вызывать ошибку. Тут есть некоторые технические причины (связанные с тем, как именно происходит поиск во внутренних таблицах имён). Но это часто оправдано тем, что в любой программе, достаточно сложной, чтобы требовать объявления констант – все переменные тоже желательно объявлять явным образом.
Наконец, рассмотрим подключение к программе уже существующих исходных файлов:
|
include (SourceFile)
|
Интерпретирует файл SourceFile как часть программы
|
Эффект от выполнения простой: всё содержимое исходного файла (имя которого задано строкой SourceFile) передаётся на вход интерпретатору (так, как будто вы ввели его сами). Чаще всего, include применяется для включения каких-то фрагментов программы, которые были вынесены в отдельные файлы (или стандартных файлов, общих для нескольких программ – например, библиотечных). Однако, include прекрасно можно использовать с интерактивном режиме. Например, если интересно, что именно делает какой-то файл исходного кода – вполне можно запустить его, не покидая интерпретатора. При успешном включении файла – include возвращает 0. Поскольку включаемый файл (в отличие, например, от декларированного функтора) не создаёт какой-то своей области видимости – все глобальные декларации в SourceFile и останутся глобальными.
Теги
Лента материалов
Соблюдение Правил конференции строго обязательно!
Флуд, флейм и оффтоп преследуются по всей строгости закона!
Комментарии, содержащие оскорбления, нецензурные выражения (в т.ч. замаскированный мат), экстремистские высказывания, рекламу и спам, удаляются независимо от содержимого, а к их авторам могут применяться меры вплоть до запрета написания комментариев и, в случае написания комментария через социальные сети, жалобы в администрацию данной сети.

