(前回の続き)
ではどう修正すればよいだろうか。「参照渡し」になるのが悪いということで、「値渡し」にする、つまり変数(レジスタ)ではなく「その値を表すトークン列」を再帰呼出の引数に入れるという方法が考えられる。この場合、「値」は整数値だから、数字列で表したものを渡せばよいであろう。つまり、\countX
、\countY
等は \doGcd
の内部のみで(数値演算のため)使われる一時変数と考えるのである。ただ一般的な話として、再帰呼出を伴う場合(より一般的には、「同じ一時変数を使用するマクロの呼出がネストした場合」)は、「一時変数」がローカルでないことの影響を考えなければならない。しかし、実は「末尾再帰」の場合は問題が起こらない。何故なら、過去の呼出における変数の値を記憶する必要がないからである。以上の考察に従って、\doGcd
を以下のように書き換える。
\def\doGcd#1#2{% \countX=#1\relax \countY=#2\relax \countZ=\countX \divide\countZ\countY \multiply\countZ\countY \advance\countX-\countZ \ifnum\countX=0 \edef\next{\countResult=\the\countY}% \else % \edef で変数を値の数字列に展開してしまう \edef\next{\noexpand\doGcd{\the\countY}{\the\countX}}% \fi \next } % テスト \doGcd{91}{42} \immediate\write16{Answer = \the\countResult} % --> Answer = 7 (正解!)
確かにこれで正常な動作となった。しかし、別の考え方もある。そもそも \countX
等は「グローバル」なのであるから、それを引数に渡す必要はないはずである。だから、引数のない再帰でループだけさせておいて、あとは変数の間で値をやり取りするという方法である。これは手続き型言語のコーディング法に近くなる。
\def\doGcd#1#2{% \countX=#1\relax \countY=#2\relax \doGcdIter } \def\doGcdIter{% % countZ := countX mod countY \countZ=\countX \divide\countX\countY \multiply\countX\countY \advance\countZ-\countX \ifnum\countZ=0 \countRes=\countY \else \countX=\countY \countY=\countZ \expandafter\doGcdIter \fi }
どちらの書き方がいいかは各人の判断に任せる。私自身は「結果的にコードが見易くなる方」を選ぶと思う。
前回の最初に挙げた \countUpTo
をこの 2 つの方法で書き換えたものを載せておく。
\newcount\countM \newcount\countN % 関数型な書き方 \def\countUpToIter#1#2{% \countM=#1\relax \countN=#2\relax \the\countM\par \ifnum\countM<\countN \advance\countM 1 \edef\next{\noexpand\countUpToIter{\the\countM}{\the\countN}}% \else \let\next\relax \fi \next } \def\countUpTo#1{% \countUpToIter{1}{#1}% } % 手続き型な書き方 \def\countUpToIter{% \the\countM\par \ifnum\countM<\countN \advance\countM 1 \expandafter\countUpToIter \fi } \def\countUpTo#1{% \countM=1\relax \countN=#1\relax \countUpToIter }