expl3 における「完全展開可能」と「制限付展開可能」の違いを説明しようと思ったが、それにはまず TeX 言語における「完全展開可能」の概念についてもっとまともな「定義」を与える必要があると感じたので、ますそこから始めることにする。ただし、以下は我流の定義で、これと異なる定義がありうることを注意しておく。*1(あと、前に述べたように、expl3 では言葉の用法が変わる。)
「完全展開可能」の定義
TeX 言語において、あるマクロが「完全展開可能」であるとは、要するに「展開だけで『正しい結果』が得られる」ということである。
当該のマクロ
\Macro
について、以下の事項が(人為的に)決められているとする:
- どういう引数が「正しい」のか?(例えば「区切り無しで 1 つの整数値をとる(
\Macro{10}
等)」のが正しい、等)- 「正しい引数」の各々について、
\Macro
にそれが伴った場合の「正しい結果」のトークン列は何か?(例えば、「引数の整数値に関わらず、常に『42
』を結果とする、等)この前提のもとで、
\Macro
に「正しい引数」が伴った任意のトークン列 S について、S を完全展開して生成されるトークン列 T が S に対する「正しい結果」と一致する。
ここで「トークン列を完全展開する」とは以下の操作を意味する。
トークン列がそれ以上展開可能なトークンを含まなくなるまで、最左にある展開可能トークンを展開する。((マクロを表すトークンは展開可能である。プリミティブには展開可能なもの(\jobname
や\number
や\expandafter
)とそうでないもの(\relax
や\def
や\kern
)がある。))
例えば
\def\gobble#1{} \def\double#1{#1#1}
と定義されている時、「A\double\gobble BC
」の展開結果は
A\double \gobble BC →A\gobble \gobble BC →ABC
となる。(展開の順番を変えると結果が異なることに注意。((ここで最左でない \gobble
が先に展開されると思う人はいないだろうが、例えば \@find@char{\@path@list}{;}
のような状況だと、他言語の「関数」の類推で、誤って引数内の \@path@list
が先に展開されると思う人は結構多いと思う。)))
完全展開可能か否かに関する例として、「与えられた(1 つの)引数が空(のトークン列)であるか」を判定するマクロ \xx@is@empty
を作ることを考える。最も基礎的に \ifx
判定を使うことにすると以下のような感じになる。
\def\@empty{} % LaTeX では定義済 \def\xx@is@empty#1{% \def\xx@tempa{#1}% \ifx\xx@tempa\@empty 〈空である場合〉 \fi }
さて、完全展開可能性について論じる時は、そもそもマクロの「結果(返り値)」を展開結果によって表す形であることが前提となる。そこで、「空である/ない場合に T/ F (の文字トークン)に展開される」という仕様(つまり「正しい結果」)にしたい。
\def\xx@is@empty#1{% \def\xx@tempa{#1}% \ifx\xx@tempa\@empty T\else F\fi }
さて、この \xx@is@empty
は完全展開可能であるか。実際に、引数が空トークン列である場合に「完全展開」を行ってみよう。
\xx@is@empty {} →\def \xx@tempa {}\ifx ... →\def 〔\xx@tempa の展開結果〕...
\def
は展開不能なのでその次の \xx@tempa
が「その時の定義内容」に展開される。((例えば、以前に \def\xx@tmpea{X}
と定義されていれば、当然 \xx@tempa
は X
に展開される。もしかしたら \xx@tempa
は展開不能かもしれないし、未定義(この場合はエラーになる)かもしれない。))これが意図した動作でないのは明らかである。本当は、\def
が「実行」されて、\xx@tempa
の意味が更新されてほしいはずである。しかし、「完全展開可能」の定義は、展開だけで「正しい結果」に至ることを要求しているのである。これまで何度か「完全展開可能なマクロでは代入操作ができない」と述べたが、これはまさにその定義に由来する。
空トークン列の判定を代入なしに行う方法として、次のようなユニークトークンを使う方法がある。\xx@uniq
はそれ自身としか \ifx
同値にならないので、\ifx
比較は #1 が空の時のみに成立するからである。*2
\def\xx@uniq{\xx@uniq@} % ユニークトークン % \xx@uniq と \xx@uniq@ は他の箇所で用いないという規約を敷く \def\xx@is@empty#1{% \ifx \xx@uniq#1\xx@uniq T\else F\fi }
この定義だと、確かに展開だけで「正しい結果」を得られる。*3
\xx@is@empty {} →\ifx \xx@uniq \xx@uniq T\else F\fi →T\else F\fi 〔if-分岐進入〕 →T 〔if-分岐脱出〕
何故「完全展開可能」が大事なのか
わざわざ「完全展開可能」という言葉があるのは、それが TeX プログラミングにおいて大事な概念であるからに他ならない。*4つまり、実際にマクロが「完全展開可能」であることが要求される場面(私はそういう場面のことを「展開限定文脈」と呼んでいる)があるということである。典型的には、\edef
(或いは \xdef
)の本体部である。
% 何か複雑なマクロを作っている \def\xx@some@macro#1#2{% \edef\xx@first@is@empty{\xx@is@empty{#1}}% #1が空かを判定 %... }
他の例として、例えば、整数 x、y を取り x を y で割った余り(の十進表記)を結果とする完全展開可能なマクロ \xx@remainder{<x>}{<y>}
があったとすると、以下のような使い方ができる。
% 整数レジスタ \@tempcnta の値を 3 で割った余りを整数値マクロ \xx@rem に代入する \edef\xx@rem{\xx@remainder{\@tempcnta}{3}}
「展開限定文脈」は \edef
の他にも存在する。例えば、「\if
の直後」「整数を読む文脈で整数表記が現れた場合」等も、「読取が完了する」まで展開が続けられる。ということは、次のようなことも可能である。
% #1が空でなければ \xx@some@macro@a を実行する \if \xx@is@empty{#1}F\xx@some@macro@a \fi % \@tempcnta を 3 で割った余りを整数レジスタ \@tempcntb に代入する \@tempcntb=\xx@remainder{\@tempcnta}{3} % \@tempcnta が 3 で割り切れるならば \xx@foo を実行する \ifnum\xx@remainder{\@tempcnta}{3}=0 \xx@foo \fi
このように、完全展開可能なマクロは、他のプログラミング言語における「関数」のような使い方ができる。ゆえに、プログラムの「部品化」の手法としては、その部品を使う側から見れば最も使いやすい方法となっているのである。
「完全展開可能」の罠
ただしここで注意すべきことがある。完全展開可能なマクロを作るのは、そうでないマクロに比べて一般的に難しく、時には極めて変態的なコーディングを要する、或いはそもそも不可能な場合がある。つまり、部品を使う側から見れば極楽であっても、作る側から見ると地獄ということである。従って、特に初級の TeX プログラマの人は、マクロを完全展開可能にすることに拘ってはいけない。絶対。
完全展開可能にしなくても、「部品」を作ることは可能である。例えば、\xx@is@empty
の場合、T/F を特定の名前のマクロに代入するという方法が考えられる。
\def\xx@is@empty#1{% \def\xx@tempa{#1}% % マクロ \xx@is@empty@result に T/F を代入する \edef\xx@is@empty@result{\ifx\xx@tempa\@empty T\else F\fi}% }
この場合、「使う側」は次のようなコードを書くことになる。
% #1が空かを判定して \xx@first@is@empty に代入する \xx@is@empty{#1} \let\xx@first@is@empty\xx@is@empty@result % #1が空でなければ \xx@some@macro@a を実行する \xx@is@empty{#1} \if\xx@is@empty@result F\xx@some@macro@a\fi
もちろん、他にも方法はある。今の \xx@is@empty
のように「戻り値」が真偽値の時に一般的なのは、\newif
で定義した「スイッチ」に値を返す方法であろう。
\newif\ifxx@is@empty@result \def\xx@is@empty#1{% \def\xx@tempa{#1}% % スイッチに結果を返す \ifx\xx@tempa\@empty \xx@is@empty@resulttrue \else \xx@is@empty@resultfalse \fi }
TeX 言語で「関数のようなもの」を作る方法については、また後日話をしたいと思う。