(前回の続き)
ではどう修正すればよいだろうか。「参照渡し」になるのが悪いということで、「値渡し」にする、つまり変数(レジスタ)ではなく「その値を表すトークン列」を再帰呼出の引数に入れるという方法が考えられる。この場合、「値」は整数値だから、数字列で表したものを渡せばよいであろう。つまり、\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
}