\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
を作ることはできないのである。