マクロツイーター

はてダから移行した記事の表示が崩れてますが、そのうちに直せればいいのに(えっ)

TeX での末尾再帰 (6)

プログラミング言語において、関数(あるいはメソッド)に与える引数の数は(静的型付け言語の場合は型も)通常は関数ごとに固定されていてその関数の呼出において変わることはない。しかし例外的に、引数の個数(や型)が呼出毎に変わりうるような関数があり、そのような関数は可変長引数をもつと呼ばれる。C 言語(や他の多くの言語)の printf() は可変長引数もつ関数の代表例である。

printf("%s\n%d\n", "参照先が見つからない", 7);

呼び出された関数の側で可変長引数をどう扱うかについては言語毎に方法に大きな違いがある。RubyJava では可変長引数の引数列が自動的に配列に変換される。

Ruby の可変長引数の取扱の例)
def func_with_vararg(first, *rest) # 可変部分に * を付した引数名を書く
  p first     # first の値を表示
  p rest      # rest の値を表示
end
func_with_vararg(1, 2, 3, 4)
# (実行結果)
# 1
# [2, 3, 4]        ←rest は配列になっている

JavaScriptPerl の場合は、「与えられた引数全てからなる配列」が常に用意されているので、それを利用して可変長引数を利用できる。((Perl では引数は @_ という名のリスト変数に参照渡しされる。他の多くの言語と異なり個々の引数に変数を割り当てる書き方がない(Perl6 は違うようだが……)。多くの場合、関数の先頭で my ($x, $y) = @_; のようにローカル変数に値をコピーする。ここで、my ($first, @rest) = @_; のように「可変部分をリストにする」こともできる。))

JavaScript の可変長引数の取扱の例)
function func_with_vararg(first) { // 可変部分は書かない
  // arguments は「全ての」引数を保持している
  print(first); // arguments[0] でも同じ
  for (var i = 1; i < arguments.length; i++) // 可変部分
    print(arguments[i]);
}
func_with_vararg(1, 2, 3, 4);
// (実行結果: 以下の引数で print が順に呼ばれる)
// 1
// 2
// 3
// 4

いずれの方法にしても、不定の個数をもつ引数を格納する配列(リスト)を用いることになる――不定複数の値を保持するのだから、配列を使うのは必然であろう。ところが、Postscript 等の「スタック型言語」*1の場合は様子が異なる。そのような言語では、関数呼出の際に引数はスタック(オペランドスタック)を通して渡される。スタックは元々不定数のデータを保持できるので、可変引数のために配列を用いる必要がないのである。

スタック型言語での可変長引数の処理はどうすればよいか。ループの中でスタックから 1 つずつ引数を取り出す(ポップする)ことになるが、引数の個数が不明なので、このままではどこまで取り出せばよいかが判らない。*2そこで、通常のデータとしては使われない「特別な値」を用意する方法が考えられる。すなわち、呼び出す側では引数列をプッシュする前に「特別な値」をプッシュしておく(つまり、スタック中では引数の値の下にそれが置かれる)。呼び出される側は、ループの中で 1 つずつ値をポップし、それが「特別な値」であれば取り出しを終了すればよい。以下の例は Postscript で可変長引数として渡された複数の数値の和を返す関数である。Postscript では「特別な値」として使うために用意された「マーク」という値があり、mark 演算子はこの「マーク」をプッシュする。*3

% mark num1 ... numn  sumall  sum
/sumall {
  1 dict begin
    /sumresult 0 def
    {
      dup mark eq { pop exit } if
      sumresult add /sumresult exch def
    } loop
    sumresult
  end
} def
% 実行例: 100 - (1 + 2 + 3 + 4) の値を表示
100 mark 1 2 3 4 sumall sub =  %=> 90

……おっと、TeX の話をしなければ。

実は、TeX での可変長引数の扱いは、先述のスタック型言語でのそれと似ている部分がある。これを見るために、あるマクロがまさに展開されようとする時点を考える。

\macroA{4}{3}{2}{1}……

ここで、\macroA の後に続く「引数」の列({4}{3}…)をスタックであると考える。*4前側がスタックの上側(トップ側)に相当する(つまりトップにある値は 4)。\macroA が 1 つの引数を取る(つまり \def\macroA#1{...} のように定義された)とすると、それを展開することで、トップにある 4 が「ポップされる」ことになる。さらに、その後に展開・実行が進んで、\macroA{4} の部分が \macroA の末尾再帰に置き換わったとすると、このマクロは 1 度に 1 つずつ引数を「スタックからポップ」しながら繰り返し処理を行うということができる。Postscript の場合と同じく、このままではどの引数までが処理対象であるかが判らないことになるが、ここでも「特別な値」を使って当該のマクロの可変長引数の終端を示すことができる。

% \sumall{{num_1}{num_2}...{num_n}} :
%   num_1 + num_2 + ... + num_n を \countResult に代入する
\newcount\countResult
\def\sumall#1{%               % {{3}{2}{1}} を
  \xx@sumall@a#1\xx@end       % {3}{2}{1}\xx@end の形に変換
}
\def\xx@end{\xx@end@}         % ユニークトークン
\def\xx@sumall@a{%
  \countResult=0
  \xx@sumall@b
}
\def\xx@sumall@b#1{% 再帰によるループ
  \ifx#1\xx@end % 何もせず終了
  \else
    \advance\countResult#1\relax
    \expandafter\xx@sumall@b % \expandafter で末尾再帰を保証
  \fi
}
% 実行例
\sumall{{4}{3}{2}{1}}
\immediate\write16{Answer = \the\countResult}
% --> Answer = 10

TeX は配列型のない言語であり、また可変長引数に関する支援もない。従って、現実のプログラミングで可変長引数を扱う場合には専らこの「引数をポップする末尾再帰」が使われているのである。いくつか注意をしておく。

  • この手法を使う場合、正しい末尾再帰の形にしておかないと、(資源を消費するという話の前に)再帰呼出を行った時の引数が正しくならず全く正常に機能しないことになる。例えば、上のコードで \xx@sumall@b\expandafter が抜けていると再帰呼出が \xx@sumall@b\fi{3}... となり失敗する。
  • 上のようにマクロ \xx@end を定義すると、「\xx@end@ という制御綴を他の箇所では一切使用しない」という条件のもとで、\xx@end\ifx の比較において自身とのみ等価と比較されるトークンとなる。単に \xx@end を未定義にしておくと、\ifx では他の任意の未定義の制御綴と等価と判断されるので注意。

*1:ところで、スタック型言語で有名なものはないのかな? 私が思いついたのは Postscript の他には Whitespace と BibTeX しかないのだが。スタック機械のアーキテクチャは現在でもよく用いられているとは対照的にスタック型言語のプレゼンスは限定的なのかな。

*2:スタックに「その関数の引数でないもの」が残っているかも知れないので全部取り出してしまうのは不適切である。

*3:ただし、Postscript には「スタック中のマークより上にある値の個数」を調べる counttomark という演算子があり、実際のプログラミングでは、これを用いて個数を調べた上でその回数だけ引数のポップを行うという処理を用いることが多いであろう。

*4:簡単のため、区切り無し引数のみを用いると仮定する。