以前に、TeX で「関数」をどう表現するか(特に「戻り値」をどう扱うか)について解説したことがある。その時に、「関数」として機能するマクロの書き方として次の 3 つを紹介した。
- 戻り値を受け取る変数をマクロの引数として渡す。
- 予め「戻り値を受け取る為の専用の変数」を用意する。
- 戻り値(を表現するトークン列)に完全展開されるマクロを作る。
実は、「関数」の実現方法にはもう 1 つあって、それが「CPS(継続渡しスタイル;Continuation Passing Style)を用いる」ことである。少しばかり変態的なので滅多に用いることはないが、しかしこの形式での実装が必要になる場合がある。それについて解説する。
簡単なプログラム課題
次の 3 つの関数 Foo()、Bar()、FooBar() を TeX のマクロ(名前を
\Foo、\Bar、\FooBarとする)として実現せよ。「関数の実現方法」は自由とする。
- Foo(英字列) は引数の英字列の最初の 3 文字を返す。ただし 3 文字に満たない場合は
Xで埋める。例: Foo(textile) =tex;Foo(a) =aXX- Bar(英字列) は引数の英字列を逆順にした英字列を返す。例: Bar(
live) =evil- FooBar(英字列) は Foo() と Bar() の合成で、Foo(Bar(英字列)) を返す。例:FooBar(
overhaul) =lua
このブログの熱心な読者*1ならば何の苦もなく済ませられるだろう。
\def\Foo#1{% Foo(#1) に完全展開される
\xx@Foo@a#1XXX\relax % X を後ろに付ける
}
\def\xx@Foo@a#1#2#3#4\relax{%
#1#2#3% 最初の3トークン
}
\def\Bar#1#2{% #1 := Bar(#2) に相当
\def\xx@tempa{}%
\xx@Bar@a#2\relax
\let#1\xx@tempa
}
\def\xx@Bar@a#1{%
\ifx#1\relax\else
\edef\xx@tempa{#1\xx@tempa}%
\expandafter\xx@Bar@a
\fi
}ここでは、\Foo を 3 の方式、\Bar を 1 の方式で実装した。この場合、これらの合成である \FooBar は 1 の方式を用いて次のように書ける。
\def\FooBar#1#2{% #1 := FooBar(#2) に相当
\Bar\xx@tempb{#2}%
% \xx@tempb を一回展開する必要がある
\edef#1{\expandafter\Foo\expandafter{\xx@tempb}}%
}
%% テスト
\FooBar\result{overhaul}
\show\result %==> luaさて、これだけなら極めて初歩的な問題であり、CPS とかが出てくる幕はない。
しかし、(当然予想される展開として、)「次の問題」が登場するわけである。
先の問題と同じ要件で、ただし今度は \FooBar が完全展開可能であるように実装せよ。
(お楽しみはまた次回)
*1:ここではその実在性は議論しない。