
Это дополнительный вопрос по теме\глобальный вариант \csname…\endcsname, который просит решения описанной там проблемы, но не требует объяснений.
Суть проблемы в том, что
{ \gdef\foo{...} }
похоже, ведет себя по-другому
{ \expandafter\gdef\csname foo\endcsname{...} }
где последний «добавляет сохраняющую запись в save_stack».
Итак, я хотел бы знать:
- Почему существует разница в поведении этих двух конструкций? Это просто побочный эффект способа
\csname...\endcsname
реализации или это намеренное семантическое различие (насколько это возможно определить). - Что
{retaining ...}
именно означает запись в файле журнала, упомянутая в связанном вопросе? Зачем нужно сохранять локальные определения после закрытия группы?
решение1
Спасибо, что задали этот вопрос. Я не совсем понял пример надругой вопроскогда я это прочитал, но этот вопрос побудил меня перечитать его еще раз, и, кажется, теперь я понимаю.
По сути, из примера не было ясно, что проблема возникает, когда у нас есть несколько таких \expandafter\gdef\csname foo\endcsname{...}
конструкций в пределахтакой жегруппа (или группы, вложенные в эту группу), т. е. стек сохранения растет только пока находится внутри группы. Сообщение «сохранение…» печатается во время выхода из группы (когда стек выталкивается), но это сообщение само по себе не является проблемой; оно лишь служит доказательством того, что стек вырос ранее.
Этот абзац, вероятно, сбил с толку, поэтому давайте разберемся со стеком сохранений с нуля. :-)
1.Рассмотрим такой пример:
\def\a{hello}
{
\def\a{world}
}
Здесь, когда TeX во внутренней группе видит \a
переопределение (в world
), он сохраняет предыдущее значение (ссылку на список токенов, содержащий hello
) в стеке сохранения. Затем, когда он достигает конца группы, он выталкивает стек, чтобы восстановить определение hello
. Это очевидная причина для стека сохранения, и ниже приведен соответствующий вывод трассировки, предполагая, что у вас есть \tracingrestores=2
и \tracinggroups=2
, а также \tracingassigns=2
и , что внутренняя группа начинается со строки 10 (скажем) — также я изменил вывод, чтобы удалить разрыв строки перед {into…}
:
{changing \a=undefined}{into \a=macro:->hello}
{entering simple group (level 1) at line 10}
{changing \a=macro:->hello}{into \a=macro:->world}
{restoring \a=macro:->hello}
{leaving simple group (level 1) entered at line 10}
2.Теперь рассмотрим тот же пример.безвверху \def\a{hello}
:
{
\def\a{world}
}
— еще раз, когда TeX видит \def\a{world}
внутри группы, он должен сохранить предыдущее значение \a
. Оно оказалось неопределенным, но нам все еще нужно, чтобы оно снова стало неопределенным после выхода из группы, поэтому TeX должен поместить «неопределенное» значение в стек сохранения. Вывод трассировки:
{entering simple group (level 1) at line 10}
{changing \a=undefined}{into \a=macro:->world}
{restoring \a=undefined}
{leaving simple group (level 1) entered at line 10}
3.Теперь рассмотрим аналогичный пример:
{
\let\a=\relax
\gdef\a{world}
}
Здесь, когда TeX видит первый \let\a=\relax
, он должен сохранить предыдущее значение («undefined») в стеке сохранения, как и в предыдущем примере. Затем, когда он видит , ему \gdef
не нужно ничего помещать в стек сохранения. Наконец, когда он достигает конца группы и начинает выталкивать свой стек (который в настоящее время содержит значение «undefined» для \a
), он замечает, что \a
теперь имеет глобальное определение, поэтому он игнорирует значение «undefined» и сохраняет глобальное определение. Это объясняет вывод трассировки:
{entering simple group (level 1) at line 10}
{changing \a=undefined}{into \a=\relax}
{globally changing \a=\relax}{into \a=macro:->world}
{retaining \a=macro:->world}
{leaving simple group (level 1) entered at line 10}
4.Наконец, рассмотрим такой пример:
{
\expandafter\gdef\csname a\endcsname{world}
}
Это оказывается точно таким же, как и в предыдущем случае. Это потому, что когда TeX видит , \expandafter
он временно проходит по \gdef
и начинает действовать по следующему токену ( \csname
) — это означает, что он действует по \csname a\endcsname
для создания макроса \a
с определением , \relax
если он еще не был определен (именно так и \csname
работает), и только после этого он действует по (ранее временно пропущенному) \gdef
и переопределяет \a
новое определение, которое следует за ( world
). Таким образом, вывод трассировки такой же, как и в предыдущий раз:
{entering simple group (level 1) at line 10}
{changing \a=undefined}{into \a=\relax}
{globally changing \a=\relax}{into \a=macro:->world}
{retaining \a=macro:->world}
{leaving simple group (level 1) entered at line 10}
Вот и все. Чтобы ответить на ваши конкретные вопросы:
(1) Это просто побочный эффект способа
\csname … \endcsname
реализации; а именно, это\let
токен для\relax
first. Однако это хорошо документировано и, следовательно, возможно, также является частью семантики (как все и ожидают).(2a)
{retaining ...}
Запись в файле журнала означает (см. стр. 301TeXbook), чторанееопределение, помещенное в стек сохранения, а именно, какое бы значение макроса ни было до того, как ему было присвоено значение\relax
,\csname … \endcsname
было проигнорировано из-за\gdef
(а глобальное определение было сохранено).(2b) «Зачем сохранять локальные определения после закрытия группы?» — Это не так, и они не сохраняются. Вместо этого вы видите, чтокогдагруппа закрывается, все определения, которые были ранее сохранены из-за локальных определений, сделанных в той же группе, теперь проверяются, и если в какой-то момент было глобальное определение, то только сейчас оно отбрасывается. В конце группы стек сохранения будет пустым (или, скорее, иметь тот же размер, что и при входе в группу).
Более конкретно, вот пример проблемы и решения наэтот вопрос. Автор вопроса определял множество макросов (по сути) внутри группы, примерно так:
{
\expandafter\gdef\csname A\endcsname{I'm A}
\expandafter\gdef\csname B\endcsname{I'm B}
\expandafter\gdef\csname C\endcsname{I'm C}
}
и т. д. Как мы видели в примерах 3 и 4 выше, это эквивалентно:
{
\let\A=\relax \gdef\A{I'm A}
\let\B=\relax \gdef\B{I'm B}
\let\C=\relax \gdef\C{I'm C}
}
и так далее. Таким образом, каждое определение выше помещает одну запись в стек сохранения (для значения до имени управляющей последовательности было \let
to \relax
by \csname ... \endcsname
), и только в конце группы все эти записи выталкиваются. Так что если таких определений слишком много; вы исчерпаете «размер сохранения».
Theпервый опубликованный ответ(Стивен Б. Сеглете) предложил сделать эквивалент того, что каждое действие \csname … \endcsname
будет выполняться на самом верхнем уровне (где в стек сохранений ничего не будет помещено).
Theвторой опубликованный ответ(Марсель Крюгер) предложил сделать что-то похожее:
{
\begingroup\expandafter\endgroup\expandafter\gdef\csname A\endcsname{I'm A}
\begingroup\expandafter\endgroup\expandafter\gdef\csname B\endcsname{I'm B}
\begingroup\expandafter\endgroup\expandafter\gdef\csname C\endcsname{I'm C}
}
где определения происходят внутри групп, которые немедленно завершаются, поэтому каждый стек немедленно извлекается: вывод трассировки (по модулю переносов строк):
{entering simple group (level 1) at line 10}
{entering semi simple group (level 2) at line 11}
{changing \A=undefined}{into \A=\relax}
{restoring \A=undefined}
{leaving semi simple group (level 2) entered at line 11}
{globally changing \A=undefined}{into \A=macro:->I'm A}
{entering semi simple group (level 2) at line 12}
{changing \B=undefined}{into \B=\relax}
{restoring \B=undefined}
{leaving semi simple group (level 2) entered at line 12}
{globally changing \B=undefined}{into \B=macro:->I'm B}
{entering semi simple group (level 2) at line 13}
{changing \C=undefined}{into \C=\relax}
{restoring \C=undefined}
{leaving semi simple group (level 2) entered at line 13}
{globally changing \C=undefined}{into \C=macro:->I'm C}
{leaving simple group (level 1) entered at line 10}
Если вас не волнует расширяемость и т. п., а вы просто пытаетесь понять стек сохранений, то предлагаемое решение выглядит так:
{
{\let\A=\relax} \gdef\A{I'm A}
{\let\B=\relax} \gdef\B{I'm B}
{\let\C=\relax} \gdef\C{I'm C}
}
теперь вы можете понять, почему стопка сохранений не растет.