Проблемы совместимости
Мы уже не раз обращали внимание на то, что при создании документов нередки конфликты между переменными, назначаемыми пользователем, и переменными, входящими в программы ядра, между функциями пользователя и встроенными функциями, между их заголовками и т. д. Ситуация усложняется при использовании пакетов расширения, поскольку в них широко используются переменные и различные функции, причем нередко обозначенные так же, как и встроенные функции.
Особенно коварны побочные эффекты в конструкциях, содержащих вспомогательные переменные, — например, в итерационных циклах, функциях вычисления суммы и произведения и т. п. Они содержат переменные-итераторы i,. j, k и т. д. Обычно избежать конфликтов можно с помощью механизма локализации итераторов. Вернемся к уже обсуждавшимся примерам. Возьмем пример с вычислением суммы:
i=2
2
Sum[i,{i,l,4}]
10
i
2
Ясно, что сумма вычисляется с применением цикла с заданным числом повторений. В его конце итератор i получает значение 4. Но глобальная переменная с тем же именем имеет значение 1=2, которое она получила до вычисления суммы с помощью функции Sum. В данном случае это достигнуто за счет того, что в теле функции переменная-итератор является локальной.
Нетрудно убедиться, что проблемы со статусом переменных возможны и в, казалось бы, изученных функциях суммирования и перемножения. На это явно указывает следующий пример:
func[x_] :=Sum[x^i, {i,4} ] {func[y] ,func[i] }
(У +У2+ У3+У4, 30}
i
2
Результат вычисления func [у] вполне понятен, тогда как вычисление func [i] носит явно обескураживающий характер. Причина его в том, что вместо символьного значения i в данном случае оказались использованы численные значения итератора i. А в этом случае функция Sum просто вычисляет численные значения. Говорят, что она работает по контексту]
А теперь рассмотрим пример с циклом For:
For [ i=l , i<=4 , i++ , Print [ i ] ]
1
2
3
4
i
5 .
На этот раз переменная i изменила свое значение в конце цикла с 2 на 5. Это говорит о том, что пользователю-программисту надо очень внимательно относиться к статусу переменных во всех итерационных, да и других программах.
Разумеется, Mathematica содержит средства для избежания подобного смешения ролей переменных. Одно из них — применение конструкции Module:
i=2
2
Module[{i},For[i=l,i<=4,i++,Print[i]]]
1
2
3
4
i
2
На этот раз захвата итератором глобальной переменной i удалось избежать. Однако этот пример носит не более чем частный характер. Вообще говоря, если переменная-итератор задается в теле функции, то она будет локальной, а если она задается за пределами функций, то глобальной.
Для разрешения подобных противоречий в системе Mathematica введен особый механизм контекстов. Напомним, что под контекстом подразумевается некоторое разъяснение характера связанных с контекстом объектов. Другими словами, это означает, что с каждым объектом системы Mathematica (например, с переменными или функциями) связан некоторый контекст. Чисто внешне контекст задается в виде Имя_контекста (обратный апостроф в конце имени и есть признак контекста).
Итак, контекст фактически является некоторым признаком объекта. Каждый объект системы Mathematica имеет свой контекст, который записывается перед именем объекта (знак «'» при этом является разделителем). Обычно он не виден, но существует. Объекты с одинаковыми именами могут иметь разные контексты и действовать по-разному — то есть по контексту. Пользователям полезно усвоить такую аналогию: контексты — это как бы разные папки со своими именами, куда могут помещаться одноименные файлы-объекты.
С другой стороны, один и тот же контекст может принадлежать разным объектам. Например, все системные переменные и встроенные функции имеют контекст System', то есть они относятся к системным объектам, а все символы, вводимые в начале работы с системой, имеют контекст Global (глобальные).
В системе Mathematica есть средства для визуализации контекстов. Прежде всего это функция Context:
Context[Tan]
System'
Context[E]
System'
Context/@Cos,Pi,Abort
{System', System' , System'}
Текущее значение контекста определяет системная переменная $Context или функция Context [ ]:
{$Context,Context[]}
{Global', Global'}
В начале сеанса работы по умолчанию действует контекст Global ~, что означает глобальный статус вводимых символов:
Context/@{q,r,w}
{Global', Global', Global'}
Однако контекст может быть заменен на любой нужный пользователю просто указанием его перед соответствующим символом или словом:
{new'q, new' w,Global'r}
{new'q, new'w, r}
Context/@{new' q,new' w,Global' r}
{new', new', Global4}
Обратите внимание на то, что символы new 4 q и new' w имеют новый контекст new s и отображаются вместе с ним (но контекст указан перед символом). А вот символ Global ~ r отображается лишь своим кратким именем. Причина этого в том, что текущий контекст есть Global 4 , а контекст new 4 отсутствует в списке контекстов (context path). Что касается символов q, r и z, то сами по себе (без новой контекстной приставки) они по-прежнему имеют контекст "Global:
Context/@{q,r,w}
{Global 4 , Global 4 , Global 4 }
Для вывода списка контекстов используется переменная $ContextPath:
$ContextPath
{Graphics 4 Animation 4 , Global 4 , System 4 }
С помощью функции Prepend можно добавить в список новый контекст, например new":
$ContextPath=Prepend[$ContextPath,"new4"]
{new', Graphics' Animation', Global', System'}
Теперь функция Context возвращает только контексты символов new'q, new'w и Global' r:
Context/@{new'q,new'w,Global'r}
{new', new', Global'}
С помощью функции Begin можно изменить текущий контекст на заданный, например Global' на new':
Begin["new''"]
new'q=5;
{q,Context[q]} {5, new'}
Теперь легко разобраться, как интерпретируются символы с разными контекстами. Любой символ, вводимый без контекстной приставки, то есть своим коротким именем, интерпретируется и выводится с этим именем, если его контекст является текущим. Если символ вводится полным именем, то проверяется, есть ли его контекст в списке $ContextPath. Если он есть, то к символу добавляется самый левый контекст из имеющихся в списке. Таким образом, по мере ввода новых контекстов, имена которых совпадают со старыми, происходит вытеснение новыми контекстами старых. Другими словами, это позволяет обновить уже имеющиеся определения, сохранив их на случай отмены старых контекстов.
Этот принципиально важный механизм модификации объектов играет решающую роль в создании пакетов расширений. В них часто уже имеющиеся функции (со старыми контекстами) заменяются новыми, одноименными с ними, но имеющими иные контексты.
Получение списков определений с контекстами
Для получения списка всех определений с заданным контекстом можно использовать функции Names [ "Context' S" ], где S — шаблон, определяющий интересующие нас имена. Например, для получения всех определений с контекстом System' можно использовать функцию Names ["System' *]. Поскольку этот список довольно большой, ограничимся примером вывода всех определений с контекстом System", начинающихся с буквы U:
Names["System'U*"]
{UnAlias, Underflow, Underoverscript, UnderoverscriptBox, UnderoverscriptBoxOptions, Underscript, UnderscriptBox, UnderscriptBoxOptions, UndocumentedTestFEParserPacket, UndocumentedTestGetSelectionPacket, Unequal, Unevaluated, Uninstall, Union, Unique, UnitStep, Unprotect, UnsameQ, Unset, Up, Update, UpperCaseQ, UpSet, UpSetDelayed, Upvalues, URL, Using)
Функция Names [ ] без параметра выводит полный список всех определений как из ядра, так и из пакетов расширений с указанием их контекстов. Таким образом, данная функция дает самую полную информацию об определениях (функциях, константах и т. д.), которые содержит текущая версия системы Mathematica.