\noexpand の動作は、どのように考えるとよいか

\noexpand の動作は、どのように考えるとよいか

- ut の投稿
返信数: 2
今月(?)、一部界隈で \noexpand が話題になっていたみたいなので、便乗して私も少
し考えてみました。
(実用性は考慮していません)
(興味本位なので、\noexpand の扱いについて何か困っているということではないです)

以下は、飽くまで「説明の試み」であって、以下の説明が正しいと主張しているわけでは
ありません。

ですから:

  (1)もっといい説明の仕方がありましたら、教えていただけると嬉しいです。

  (2)以下の説明が間違っていて、以下の説明には当てはまらない例がありましたら、
     やはり教えていただけたら嬉しいです。

なお、「もっと正確な説明」があるだろうなということは承知していますが、TeXbook よろしく
"deliberate lying" な説明のほうが、私のような素人には助かります。


=================================================================
\noexpand の動作についての "仮説"
(以下の説明が正しいという保証は一切ありません)
=================================================================

コントロールシーケンス \cs が展開可能なトークンであるとする。

このとき、"\noexpand\cs" は、展開処理に曝されるとまず \cs へと展開される。この
\cs は、引き続き間髪入れずに展開処理に曝されると、一時的に \relax の意味となって
展開が抑制されていて、展開はされずに、そのまま胃へと送られる。
(\edef の置換テキスト部分の \noexpand は、この場合に相当する)

他方、"\noexpand\cs" が一旦 "展開が抑制された \cs" へと展開された後、何らかの処理
が挟まった後に、再び展開処理に曝された場合には、"展開可能な \cs" へと展開された後、
実際に、展開される。

「展開処理に曝される」場合としては、\edef による定義の場合の他、TeX の口で処理さ
れるときや、\expandafter によって展開されるとき、また、カウンタへの代入の際にど
こまでが数値であるのかを事前に(?)スキャンされるとき、などがある。


【例 1】 TeX の口で 1 回展開され、引き続き TeX の口の展開処理に曝された場合

例えば:

  \noexpand\foo

が、TeX の口による展開処理に曝された場合には、"\noexpand\foo" はまず:

  \foo

へと展開される。続いて、間髪入れずに TeX の口による展開処理はこの \foo を展開し
ようとするが、この \foo は一時的に \relax の意味になっているので展開は抑制されて:

  \foo

のママ、胃へと送られる。


【例 2】 1 回だけ展開した場合

例えば:

  \expandafter\meaning\noexpand\foo

が、TeX の口による展開処理に曝されると、\expandafter が展開されて、その働きによ
り "\noexpand\foo" が 1 回展開されるので:

  \meaning\foo

となる。ここで \foo は一旦 \relax の意味になっているので、結果として:

  \relax

と出力される。


【例 3】 断続的に 2 回、展開処理に曝された場合

例えば:

  \def\foo{bar}

と定義されているとき:

  \expandafter\expandafter\expandafter\meaning\noexpand\foo

は、まず、TeX の口によって最初の \expandafter が展開されて、その働きにより、3 番
目の \expandafter が展開されて、その働きにより、"\noexpand\foo" が展開されるので、
まず:

  \expandafter\meaing\foo

となる。次に、TeX の口が遭遇するのはまた \expandafter であって、この \expandafter
が展開されることによって、\foo が再度展開されて "普通の \foo" となり、結果として
その意味が:

  macro:->bar

と出力される。

この場合には、"\noexpand\foo" が 1 回展開されて \foo となった後に、2 番目の \expandafter
の展開という処理が挟まっていて、"\noexpand\foo" が連続して展開処理に曝されている
のではないので、2 回目の展開処理に曝されると \foo は "展開可能な \foo" となって、
その意味が "macro:->bar" と出力されている。


【例 4】 断続的に 3 回展開した場合

\expandafter を 7 個使って "\noexpand\foo" を 3 回展開してみると、\meaning の結
果は:

  the letter bar

と出力される。


【例 5】 カウンタへの値の代入の後ろに続く場合

例えば:

  \newcount\countA
  \def\foo{456}

と定義されているとき:

  \countA=123\noexpand\foo

は、まず TeX は、\countA に代入される数値を確定するために、スキャンをする。"123"
の次に展開可能なコントロールシーケンスが続いているので、これが数値に展開されるか
も知れないので、それを一回展開する。"\noexpand\foo" は \foo に展開されるが、これ
は一旦 \relax の意味なので、数値ではない。

したがって、\countA には、"123" が代入される。

続いて、TeX の口は \foo に遭遇するが、この場合、"\noexpand\foo" の一回展開の後に、
\countA への値の代入という処理が挟まっているので、TeX の口による展開処理は、\foo
を展開できてしまう。

したがって、"456" が出力されることになる。


\documentclass{article}
\begin{document}

\ttfamily

1: \noexpand\foo

2: \expandafter\meaning\noexpand\foo

3: \def\foo{bar}\expandafter\expandafter\expandafter\meaning\noexpand\foo

4: \expandafter\expandafter\expandafter\expandafter\expandafter%
  \expandafter\expandafter\meaning\noexpand\foo

5: \newcount\countA\def\foo{456}\countA=123\noexpand\foo

\end{document}


※ 繰り返しになりますが、以上の説明は、私立文系初級ユーザーである私による「説明
  の試み」であって、以上の説明が正しいという保証は、まったくありません。
ut への返信

Re: \noexpand の動作は、どのように考えるとよいか

- Z. R. の投稿
>以下の説明には当てはまらない例

例えば以下の例はうまく説明できないのではないでしょうか。

\def\bar{BAR}\def\foo{FOO}
\expandafter\bar\noexpand\foo
%==>出力は"BAR"

2行目の一回展開が

\bar‹展開抑止の\foo›

となり、その後に「\bar の展開」「文字トークン BAR の実行」が起こります。ということは

>何らかの処理が挟まった後に、再び展開処理に曝された場合には、"展開可能な \cs" へと展開された後、実際に、展開される。

の「何らかの処理が挟まった後に、再び展開処理に曝された場合」に該当するはずなので、この説明に従うと仮定すると“BAR”の後に“FOO”が出力されるはずです(実際はそうならない)。

Z. R. への返信

Re: \noexpand の動作は、どのように考えるとよいか

- ut の投稿
おはようございます。お返事をありがとうございます!

> この説明に従うと仮定すると“BAR”の後に“FOO”が出力される
> はずです(実際はそうならない)。

ありゃ。こんなシンプルな反例があるとは…orz。
(理屈じゃなくて、観察に基づく推論の場合には、すべての場合を
カバーするように注意深くサンプルを選ばないと、こういうことに
なってしまうのですね…)

【例2】で挙げた:

  \expandafter\meaning\noexpand\foo

の場合と同様に:

  \expandafter\bar\noexpand\foo

の一回展開では "\noexpand\foo" が \relax になっているのでしょ
うけれど、それが、\bar の展開の後に、再度展開処理に曝された際、
\relax のママ実行されているのですね…。

> 「何らかの処理が挟まった後に、再び展開処理に曝された場合」

という考え方でうまく説明できるんじゃないかなと思ってしまった
のですが、ダメでしたか~。

でも、ご応答をいただいて、自分の考えの足りていない点をご指摘
いただけると、考えるヒントやチャンスをいただけるので、とても
ありがたいです。ありがとうございます。
(さて、それでは、どう考えたら、厳密ではなくとも、\noexpand
の動作がうまく説明できるのでしょう…)