\romannumeral と \kansuji の両プリミティブの用法について、自信のない人は手許の TeX の参考書を参照して確認してほしい(おっと、\kansuji は「pTeX の参考書」が必要か)。しかし、文献によっては、最も基本的な使い方である「\romannumeral トリック」と「\kansuji トリック」についての記述が不十分な可能性もあるだろう。そこで、このブログで解説することにした。まずは、\romannumeral を紹介する。
\romannumeral の基本的な使い方
\romannumeral プリミティブは以下のような書式で用いられる。
\romannumeral-`0<トークン列S>
そして、このトークン列を一回展開した結果が、<トークン列S> の“先頭完全展開”、すなわち「先頭が展開不能トークンになるまで展開を反復した結果のトークン列」になる。(後で述べるように、これには例外が一つある。)
例えば、以下のように制御綴が定義されていたとする。
\def\A{\B\B}\def\B{\C\C}\def\C{\D\D}
\let\D\relax % \D は展開不能この時、「\A\A」というトークン列について、展開を繰り返すと、
\A \A → \B \B \A → \C \C \B \A → \D \D \C \B \A
となり、この時点で列の先頭に展開不能トークン \D が現れる。従って、この「\D\D\C\B\A」が「\A\A」を“先頭完全展開”した結果ということになる。そして、\romannumeral を利用すると、“先頭完全展開”がただ一回の展開に転化することができる。
\romannumeral-`0\A\A → \D\D\C\B\A
\romannumeral を使って「関数を合成」する
このように \romannumeral を使うと展開を“加速”することができる。この性質は「完全展開可能なマクロの実装」において威力を発揮する。「完全展開可能なマクロ」はトークン列の間の“関数”と見なすことができるが、\romannumeral を使うと、そのような“関数”を“合成”することができるのである。
例えば以下のようなマクロ \chop を考える。*1これは引数の文字列(空白を含まない)について、末尾の文字を取り除いた文字列を返す。
%% \chop{<トークン列X>}
% 引数Xは"文字列"(カテゴリコード11または12の文字トークンから
% なるトークン列)とする。Xの末尾のトークンを除いたトークン列
% に展開される。Xが空列の場合は空列になる。先頭完全展開可能。
\def\chop#1{\xx@ifmt{#1}{}%
{\xx@chop@a\xx@mk#1\xx@nil}}
\def\xx@chop@a#1\xx@mk#2#3\xx@nil{%
\xx@ifmt{#3}{#1}{\xx@chop@a#1#2\xx@mk#3\xx@nil}}
\def\xx@mrk{\xx@mrk@}\def\xx@nil{\xx@nil@}
\def\xx@ifmt#1{\ifx\xx@mrk#1\xx@mrk\expandafter\@firstoftwo
\else\expandafter\@secondoftwo\fi}(先頭)完全展開可能であるので、次のように \typeout の中で使うことができる。
\typeout{[\chop{hello}][\chop{}]}%==>「[hell][]」と表示
「1 文字切り落とす“関数”」が得られているので、これを自分自身と“合成”すれば「2 文字切り落とす“関数”」が作れそうである。そこで問題であるが、実際に \chop を利用して「2 文字切り落とす」ための完全展開可能なマクロ \choptwo を実装してほしい。ただし、\chop の実装については、先に示した実装コードに記された「仕様」以上のことを仮定しないものとする。どうすればよいであろうか。
もちろん、次のような“最も素朴な実装”ではダメである。
\def\choptwo#1{%
\chop{\chop{#1}}}\chop の引数は、仕様上、“文字列”(文字トークンの列)でなければならない。ところが、\choptwo{filll} のように呼び出した場合、外側の \chop の引数になるのは「\chop{filll}」であり条件を満たしていない。希望としては、引数になるのはこのトークン列を完全展開した結果の「fill」であってほしい。・・・・・・となると、\expandafter をたくさん並べればよさそうである。
ところが、実際にはそれは上手くいかない。何故なら、「\chop{filll}」を何回展開すれば「fill」が得られるのかが事前に判らないからである。\expandafter 鎖を用いて引数を事前に展開させようとすると、「n 回展開するのに 2n−1 個の \expandafter が必要」なわけだから、事前に(十分な)展開回数を決めておいてそれに応じた個数の \expandafter を配置する必要がある。ところが、\chop の仕様では展開回数は示されていないし、仮に \chop の実装に依存することを許したとしても、先の \chop の実装では、引数の文字列の長さが増えるに応じて、必要な展開回数は幾らでも増えていってしまう。これでは、「幾ら \expandafter を並べても足りない」のである。
何回展開が必要であっても完全に展開してくれるもの、といえば、\edef が思い浮かぶであろう。しかしもちろん、\edef は代入操作の一種だから、完全展開可能なマクロを実装する際には使えない。結局のところ、ここで必要なのは、“完全展開可能な \edef”である。そして、その役目を(部分的に)果たしてくれるのが \romannumeral なのである。
実際に \romannumeral を用いて問題を解決してみよう。まず、さっきの \choptwo の“ダメな実装”で、内側の \chop に \romannumeral トリックを付したものを考えてみる。
\def\choptwo#1{%
\chop{\romannumeral-`0\chop{#1}}}なんと、これだけで、先に述べた「事前に展開回数が判らない」問題が消滅してしまう。「\romannumeral-`0\chop{filll}」はただ 1 回の展開で「fill」になることが判っているからである。あとは、引数を 1 回展開させるための単純な \expandafter 鎖を組み合わせればよいだけである。
\def\choptwo#1{%
\expandafter\chop\expandafter{\romannumeral-`0\chop{#1}}}これで完成である。試してみよう。
\typeout{[\choptwo{filll}][\choptwo{A}]}%==>「[fil][]」すばらしい。
この技法が理解できたのであれば、「3 文字切り落とす」マクロ \chopthree を実装するのも極めて容易である。次のように、\chop と \choptwo を“合成”すればよい。((\chop と \choptwo を入れ替えたコードでもよいが、それは「\choptwo も先頭完全展開可能である」からであることに注意。))
\def\chopthree#1{%
\expandafter\choptwo\expandafter{\romannumeral-`0\chop{#1}}}先頭完全展開可能、が必要
ここで注意であるが、\romannumeral を用いて「関数の合成」を行う場合、対象のマクロは“先頭完全展開可能”、つまり「“先頭完全展開”するだけで“結果”のトークン列が得られる」ようなものでなければならない。
例えば、先の例の \chop を次のように実装したとする。
\def\chop#1{\xx@ifmt{#1}{}%
{\xx@chop@a#1\xx@nil}}
\def\xx@chop@a#1#2\xx@nil{%
\xx@ifmt{#2}{}{#1\xx@chop@a#2\xx@nil}}
% \xx@ifmt 等は前掲のコードと同じこの \chop の実装は“完全展開可能”であるので \def や \typeout の中で用いることができる。しかし、実際の \chop の展開の過程をみると次のようになっている。
\chop {filll} (1)
→ ……(略)……
→ f\xx@chop@a illl\xx@nil (2)
→ ……(略)……
→ fill (3)\edef 中で行われる“完全展開”では、(2) のように先頭に展開不能トークンが現れてもその後に展開可能なトークンがあればその展開が行われ、(3) まで移行する。それに対して、“先頭完全展開”の場合は (2) の段階で展開が止まってしまう。従って、「\romannumeral-`0\chop{filll}」を 1 回展開した結果も「f\xx@chop@a illl\xx@nil」になってしまう。ゆえに、この \chop の実装では“合成”して \choptwo を作ることはできないのである。