マクロツイーター

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

TeX での末尾再帰(4)

前回の続き)

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