今まで\expandafterや\futureletや\afterassignmentといったTeX言語の「変態的プリミティブ」についての実用的な解説記事を書いてきました。今回は変態プリミティブの中でも「最凶」との呼び声が高い\noexpandについて扱います。
前提知識
- 初級編までの内容であれば「トークンとは何か」「完全展開とは何か」について知っていれば十分です。むしろ(一回展開の理解が必要な)
\expandafterよりも簡単
なくらいです。 - それより先については、まあ、読めるところまで読んでみましょう
ソレとどう対峙すべきか
\noexpandを攻略する際の大切な心構えは「正面から“\noexpandの仕様”に立ち向かおうとしない」ことです。\expandafterの記事で書いたように、\expandafterについては「下手な小細工をせずに素直に“\expandafterの定義”を習得する」のが一番の近道だと考えています。しかし凶悪な\noexpandに対して同じことをすると深い沼に沈んで行って二度と帰れなくなります ![]()
そこで、\noexpandの習得は「一般的な“\noexpandの定義”の追求は敢えて避けて、実用的な使われ方のパターンを抽出してそれぞれの場合の動作を把握する」という方針に従うことにします。実のところ、パターンはそれほど多くありません。数個のパターンのそれぞれの場合の動作を把握すれば、それだけで、事実上「\noexpandの動作を把握した」ことになるでしょう。
\futureletの記事と同じく、本記事では「\noexpandを利用したコードが読めるようになる」ことを重視します。
パターン別\noexpand攻略法
それでは実際に、\noexpandが実用的に用いられるパターンと、そこでの\noexpandの意味について解説していきましょう。
なお、本節のコード例においては、以下のマクロが予め定義されていると仮定します。
\def\xA{\xB}
\def\xB{\noexpand\xC}
\def\xC{DEF}
【初級😊】展開限定文脈でのソレ
\noexpandが使われる場面の圧倒的大多数は「完全展開する場所(厳密にいうと展開限定文脈)で使う」というものです。これが\noexpandの最も基本的な使い方で、この場合の規則は次のようになります。
-
展開限定文脈において
\noexpand‹トークンA›を完全展開すると‹トークンA›になる。つまり、たとえ‹トークンA›が展開可能だったとしてもそれ以上は展開されない。
\edefの定義本体中のソレ
展開限定文脈として一番有名なのが「\edef・\xdefの定義本体(置換テキスト)の中」です。展開限定文脈での\noexpandの動作を\edefを例に説明してみます。
\edef\xTestA{\noexpand\xC\xC}
\show\xTestA %==> macro:->\xC DEF
この\edef文を実行すると定義本体の\noexpand\xC\xCが以下の手順に従って完全展開されます。
-
\noexpand\xCを完全展開すると\xCとなり、それ以上展開できないので次のトークンに進む。 - 次の
\xCは\noexpandが付いてないので普通に完全展開してDEFとなる。
以上より結果は\xC D E Fとなり、このトークン列を定義本体とするマクロが\xTestAに代入されます。
もちろん、\noexpand以外のトークンについては今まで通りの完全展開の規則に従います。マクロ展開の結果として\noexpandが現れる場合を考えてみましょう。
\edef\xTestB{\xA}
\show\xTestB %==> macro:->\xC
ここで定義本体の\xAを完全展開すると以下のようになり、結果は\xCとなります。
\xA
→ \xB
→ \noexpand\xC
→ \xC (それ以上展開されない)
イロイロな展開限定文脈でのソレ
展開限定文脈とは「トークンの展開は起こるが、展開不能なトークンの実行は行われない」という場所のことです。\edef(と\xdef)の定義本体部が代表的ですが、その他に以下のような箇所が該当します。
-
\writeの引数1
※LaTeXの\typeoutは内部で\writeを使っています。 -
\message/\errmessageの引数 -
\specialの引数2 -
\expandedの引数
※\expandedは比較的新しい拡張プリミティブ3で「引数のトークン列を完全展開する」という機能4をもちます(参照)。
\writeプリミティブを使った例を示します。\noexpandの扱いについて\edefと全く同じであることが判ります。
% 完全展開系を端末に出力する
\immediate\write16{\noexpand\xC\xC}
%==> "\xC DEF"と端末に出力
実はここで挙げた項目の他にも展開限定文脈は存在して、例えば「\csnameと\endcsnameの間のトークン列」も該当します。展開限定文脈である以上、その中に現れた\noexpandについては先述の規則が適用されます。
ただし、\csnameについては「間のトークン列は文字トークンから成る必要がある」という制約があるため、\noexpand付きのトークン5を含められません。
\csname\noexpand\xC\endcsname
%==>エラー: Missing \endcsname inserted.
おまけ:先頭完全展開でのソレ
これまで\noexpandを完全展開する話をしてきましたが、「先頭完全展開」の概念を知っている人なら\noexpandを先頭完全展開した場合が気になったかもしれません。結論としては、完全展開と同じ結果になります。
-
\noexpand‹トークンA›を先頭完全展開すると‹トークンA›になる。(たとえ‹トークンA›が展開可能だったとしてもそれ以上は展開されない。)
次のコード例では\xAの先頭完全展開形をトークン列レジスタに代入しています。
% "\xA"の先頭完全展開を \toks0 に代入
\toks0\expandafter{\romannumeral-`>\xA}
\showthe\toks0 %==> \xC
\xA→\xB→\noexpand \xCと展開した後に\xCで展開が止まるので、先頭完全展開の結果は\xCとなります。
ここまでくると一回展開も考えたくなるかもしれませんが、\noexpandの一回展開は極めて不可解な挙動を示します。
\let\x\expandafter
% "\noexpand\xC"の1回展開を \toks0 に代入
\toks0\x{\noexpand\xC}
\showthe\toks0 %==> \xC
% "\noexpand\xC"の2回展開を \toks0 に代入
\toks0\x\x\x{\noexpand\xC}
\showthe\toks0 %==> \xC
% "\noexpand\xC"の3回展開を \toks0 に代入
\toks0\x\x\x\x\x\x\x{\noexpand\xC}
\showthe\toks0 %==> DEF
底なし沼への入り口なので引き返すのが身のためです![]()
【中級😟】\if・\ifcatでのソレ
TeXの\ifは「2つの文字トークンについてその文字コードが同じであるか」を判定する条件トークンです。\ifcatは文字コードの代わりにカテゴリコードを比較します。
% (マクロ引数 #1 が単一の非アクティブな文字トークンである前提)
% #1 が(カテゴリ不問で)文字"Z"ならば
\if#1Z ‹処理› \fi
% #1 が英字のトークンならば
\ifcat#1a ‹処理› \fi
\if・\ifcatは「引数(後続トークン)が展開可能の場合は展開される」という特徴6をもちます。マクロ引数#1を比較対象とする場合、それが「非アクティブ7な文字トークンである」ことが既知であれば前掲のコードで十分ですが、そうとは限らない場合は展開への対処が必要です。
こういう場合に\noexpandが使えます。具体的には以下のような規則になっています。
-
\if・\ifcatの引数の位置にあるトークンに\noexpandが付いて\noexpand‹トークンA›となっている場合は(たとえ‹トークンA›が展開可能であっても)常に‹トークンA›を比較対象とする。
※どちらの引数にも\noexpandが付けられる8。
先ほどのコードの場合、展開可能かもしれない引数#1の前に\noexpandを付けると対処できます。
% (マクロ引数 #1 が単一トークンである前提)
% #1 が(カテゴリ不問で)文字"Z"ならば
\if\noexpand#1Z ‹処理› \fi
% #1 が英字のトークンならば
\ifcat\noexpand#1a ‹処理› \fi
展開抑止のためにソレを付ける
実際の例として「引数の単一トークンが英字トークンであるか」を判定する\xCheckLetterを実装してみます。
%% \xCheckLetter<トークン>
% <トークン>の意味が英字の文字トークンであるかを判定する.
% ※<トークン>は単一トークンに限る.
\def\xCheckLetter#1{%
\message{\ifcat\noexpand#1aYES\else NO\fi}%
}
% (plain/LaTeXの初期状態で)
\xCheckLetter a %==> YES
\xCheckLetter T %==> YES
\xCheckLetter \futurelet %==> NO
\xCheckLetter \expandafter %==> NO
\xCheckLetter ~ %==> NO
比較先が普通の文字トークンである場合は比較元の#1に\noexpandを付ければ十分です。(#1が展開可能トークンならば判定は偽になります。)
もっとヤヤコシイ比較のためにソレを付ける
先の例では単純な場合を扱いましたが、場合によっては「\noexpandを付けたトークンがどう解釈されるか」を考慮する必要があります。\if・\ifcatの引数が\noexpand‹トークンA›である場合、実際の比較対象は以下のようになります。
-
‹トークンA›が展開不能の場合は\noexpandは無意味であり、単なる‹トークンA›と同等になります。非アクティブな文字トークンは全てこちらに該当します。 -
‹トークンA›が展開可能な制御綴の場合は、展開不能な制御綴(\relax等)を与えたのと同等、つまり「文字トークンではない」という扱いになります9。 -
‹トークンA›が展開可能なアクティブ文字の場合は、‹トークンA›の外見10の文字コードとカテゴリコード(これは常に13)が比較対象になります。
この規則を利用すると、例えば「トークンがアクティブ文字トークンであるか」の判定が行えます。(ただしこれは展開不能なアクティブ文字がある場合には期待通りに動作しない11。)
% (マクロ引数 #1 が単一トークンである前提)
% 引数 #1 がアクティブ文字トークンであるか
\ifcat\noexpand#1\noexpand~ ‹処理› \fi
【上級🤯】展開可能判定のためののソレ
先の説明に少し出てきましたが、‹トークンA›が展開不能であるときは\noexpand‹トークンA›の一回展開は‹トークンA›自身になります。そしてこれは逆も成り立っていて、\noexpand‹トークンA›の一回展開と‹トークンA›が\ifx-等価になるのは‹トークンA›が展開不能のときに限られます。この性質を利用して「トークンが展開可能であるか」を判定するコードが書けます12。
% (マクロ引数 #1 が単一トークンである前提)
% 引数 #1 が展開*不能*なトークンであれば
\expandafter\ifx\noexpand#1#1 ‹処理› \fi
展開可能性判定のコードはほぼこの形に決まっているので、イディオムとしてこのまま覚えてしまってかまいません。
実際の例として「引数の単一トークンが展開可能であるか」を判定する\xCheckExpandableを実装してみます。
%% \xCheckExpandable<トークン>
% <トークン>が展開可能かを判定する.
% ※<トークン>は単一トークンに限る.
\def\xCheckExpandable#1{%
\message{\expandafter\ifx\noexpand#1#1NO\else YES\fi}%
}
% (plain/LaTeXの初期状態で)
\xCheckExpandable $ %==> NO
\xCheckExpandable ~ %==> YES
\xCheckExpandable \parindent %==> NO
\xCheckExpandable \jobname %==> YES
\xCheckExpandable \aftergroup %==> NO
\xCheckExpandable \noexpand %==> YES
まとめ
というわけで皆さん、\noexpandには深入りしないように気を付けましょう!![]()
-
ちなみに、
\writeの引数の完全展開は「実際に書出が行われる」時点で行われます。\immediate付きの場合は「\writeプリミティブを実行した時点」で展開が起こりますが、遅延されている(\immediate無し)場合は展開も遅延されます。 ↩ -
\specialの引数の展開は「\specialプリミティブを実行した時点」で行われます。ところが2023年頃に(e-TeX拡張をもつエンジンにおいて)\specialの機能拡張が行われて、\special shipout{...}とすることで展開を(\writeと同様に)遅延させられます。 ↩ -
元々LuaTeX系エンジンのみが
\expandedをサポートしていたのですが、2019年頃に(e-TeX拡張をもつ)全てのエンジンでサポートされるようになりました。 ↩ -
厳密にいうと、
\expandedは展開可能プリミティブで、\expanded{‹トークン列›}の一回展開が‹トークン列›の完全展開になります。 ↩ -
「
\noexpand+アクティブ文字」は通ってもよさそうなんですが、実際には(アクティブ文字の意味にかかわらず)エラーになるようです。 ↩ -
もう少し厳密にいうと、
\if・\ifcatは自身の直後に展開不能なトークンが2つ現れるまで完全展開を続けて、その結果現れた2つのトークンを比較対象とします。\if・\ifcatの引数が展開される理由は、本来は「文字トークン同士を比較する」ことを目的としていて、展開可能なトークン自体を比較対象とすることは目的から外れるからです。 ↩ -
文字トークンがアクティブ(active)であるとはカテゴリコードが13であることを意味します。アクティブな文字トークンは制御綴(control sequence)と同じく「代入可能である」という性質を持ちます。一方で非アクティブな文字トークンは常に展開不能です。 ↩
-
前述の通り、
\if・\ifcatは展開不能なトークンが2つ表されるまで展開を繰り返すわけですが、この手順の際に、\noexpand自体は展開可能なトークンと見なされ、かつ\noexpand‹トークンA›の展開結果は必ず「何らかの展開不能な単一のトークン」と見なされるわけです。 ↩ -
『TeXブック』的にいうと「文字コードが256、カテゴリコードが16と見なされる」ことになります。もっとも今の拡張TeXエンジンでは「文字コード256やカテゴリコード16が実際に存在する」可能性があるので、むしろ「文字コードもカテゴリコードも−1」とした方が妥当だと思いますが。 ↩
-
つまり、
‹トークンA›がもし~であれば(意味は無関係で)文字コードは126、カテゴリコードは13であるということです。\if・\ifcatは通常はトークンの意味の方を比較対象とするのですが、ここに挙げた項に該当する場合にだけ例外的に外見の方を採用します。 ↩ -
たとえ「
#1が展開不能なアクティブ文字トークンではない」ことを仮定できたとしても、「マクロ実行時のアクティブな~の意味が展開不能である場合」に問題が起こります。まあ、plainやLaTeXにおいては「~の定義は展開可能である」と仮定するのも妥当と思いますが。(完全展開可能性を要求しないなら「一時的に~に代入する」という手段がある。) ↩ -
\if・\ifcatとは異なり\ifxは引数(後続トークン)を展開しないため、\noexpand#1を敢えて展開させるために\ifxに\expandafterを被せる必要があります。 ↩