セーブスタックエントリに関連するローカルグループ内のグローバル定義の意味の違い

セーブスタックエントリに関連するローカルグループ内のグローバル定義の意味の違い

これはフォローアップの質問です\global は \csname…\endcsname の変形です、そこで説明されている問題に対する解決策を求めていますが、説明は求めていません。

問題の要点は

{ \gdef\foo{...} }

異なる動作をするようだ

{ \expandafter\gdef\csname foo\endcsname{...} }

後者は「save_stack に保持エントリを追加します。」

そこで私は次のことを知りたいのです:

  1. これら 2 つの構造の動作に違いがあるのはなぜでしょうか。これは実装方法による副作用にすぎないのでしょう\csname...\endcsnameか、それとも意図的な意味の違いなのでしょうか (わかる限り)。
  2. リンクされた質問に記載されているログ ファイル内のエントリは、正確には何を{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\aworld

{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}
}

こうすることで、セーブ スタックが成長し続けない理由がわかります。

関連情報