
これはフォローアップの質問です\global は \csname…\endcsname の変形です、そこで説明されている問題に対する解決策を求めていますが、説明は求めていません。
問題の要点は
{ \gdef\foo{...} }
異なる動作をするようだ
{ \expandafter\gdef\csname foo\endcsname{...} }
後者は「save_stack に保持エントリを追加します。」
そこで私は次のことを知りたいのです:
- これら 2 つの構造の動作に違いがあるのはなぜでしょうか。これは実装方法による副作用にすぎないのでしょう
\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
、前の例と同様に、以前の意味 (「未定義」) を保存スタックに保存する必要があります。次に を見ると、\gdef
保存スタックに何も置く必要はありません。最後に、グループの末尾に到達してスタック (現在 の「未定義」の意味を含む\a
) をポップし始めると、 がグローバル定義を持つようになったことがわかり\a
、そのため「未定義」の意味を無視してグローバル定義を保持します。これは、トレース出力を説明しています。
{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}
以上です。具体的なご質問にお答えします。
\csname … \endcsname
(1) これは実装方法の副作用に過ぎません。つまり、最初\let
にトークンが来ます\relax
。ただし、これは十分に文書化されており、したがってセマンティクスの一部であると言えるでしょう (誰もが期待しているように)。(2a)
{retaining ...}
ログファイル内のエントリは、(301ページ参照)TeXbook について)は以前\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}
}
などなど。したがって、上記の各定義は保存スタックに 1 つのエントリを配置し (制御シーケンス名がによって に\let
なる前の意味)、グループの最後でのみこれらのエントリがすべてポップされます。したがって、このような定義が多すぎると、「保存サイズ」が不足します。\relax
\csname ... \endcsname
の最初に投稿された回答\csname … \endcsname
(Steven B. Segletes 氏による) は、それぞれを最上位レベル (保存スタックに何も置かれないレベル) で実行するのと同等のことを行うことを提案しました。
の2番目に投稿された回答(Marcel Krüger 著) は、以下と同等のことをすることを提案しました。
{
\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}
}
こうすることで、セーブ スタックが成長し続けない理由がわかります。