マクロツイーター

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

TeX で何か関数のようなものを (2)

前回の続き)

正しい方法その 1: 戻り値をある特定の「変数」に代入させる

ところで、先の失敗例の \xx@remainder のコードをもう一度見てみよう。最後の(失敗する)\the\xx@a の直前の時点で、\xx@a に「戻り値」になっている。だとすれば、それをそのまま「規約」とすることが考えられる。つまり、\xx@remainder の戻り値を \@tempcnta に代入したいならば、\@tempcnta=\xx@a とすればよい、ということである。ただ、\xx@a という一時変数は規約に用いるのは相応しくないので、代わりに、「整数の戻り値を受け取る専用のレジスタ」である \xx@res@count を用意して、\xx@remainder の戻り値もそこに代入することにしよう。つまり \xx@remainder の規約は次のようになる。*1

  • \xx@remainder{<整数1>}{<整数2>}
    〈整数1〉を〈整数2〉で割った余りを整数レジスタ \xx@res@count に代入する。

この規約に従った \xx@remainder のコードは以下のようになる。

\newcount\xx@res@count
\def\xx@remainder#1#2{%
  \xx@a=#1\relax
  \divide\xx@a#2\relax
  \multiply\xx@a-#2\relax
  \advance\xx@a#1\relax
  \xx@res@count=\xx@a % 「戻り値」用変数に代入
}

この定義の場合、「100 を 42 で割った余りを \@tempcnta に代入する」という処理は以下のように書ける。

\xx@remainder{100}{42}\@tempcnta=\xx@res@count
% テスト
\typeout{\the\@tempcnta} %==> 16

なお、複数の「関数」がある場合に「戻り値用変数」を別にするかについて、私の勧める方式を述べておく。マクロ毎に戻り値用変数を用意するのでなく、(ある名前空間内で)型(整数、寸法、…)が同じ戻り値は同じ変数に格納するのがよいと思う。つまり、\xx@〜 という整数を戻り値とする「関数」的マクロは必ず \xx@res@count を用いるということである。その代わり、戻り値用変数は原則的に他の用途には使わない、((先述の \xx@remainder の定義で単純に \xx@a を全部 \xx@res@count に置き換える定義も可能だが、この理由のためそうしなかった。))つまり戻り値を参照する場合は、(非常に単純な場合を除いて*2)必ず一旦他の変数に代入する。これで本物の関数を使っている時の書き方から大きく外れることなく「関数的マクロ」を使えるだろう。

同じ方法で \xx@in@five@digits のマクロも実装してみよう。この場合、「戻り値」はトークン列(文字列)であるから、「マクロ \xx@res@macro に代入」する、すなわち「\xx@res@macro を戻り値のトークン列に(1 回で)展開される無引数マクロとして定義」することにしよう。((このように「変数」として使われる無引数マクロを私は「トークン列マクロ」と呼んでいる。「トークン列変数」の呼称は「toks レジスタ」と紛らわしいので避けている。(なお、戻り値を toks レジスタに格納するという手もあるはずだが、何故か toks レジスタは特殊な用途を除いて実際の TeX プログラムでは使用されていない。)expl3 で \tl_new:N で宣言される変数は正式に「トークン列変数(token list variable)」という名称であり、これの実体は「トークン列マクロ」である。))

\def\xx@in@five@digits#1{%
  % 数値(#1)を10進表記に変換 (#1自体は10進表記以外の可能性がある)
  \xx@a=#1\relax % 一旦変数に代入
  \edef\xx@temp{\the\xx@a}% \xx@temp は必ず10進表記になる
  % \xx@in@five@digits@a に渡して完全展開(\edef)させた結果のトークン列を
  % \xx@res@macro に代入する.
    % \xx@in@five@digits@a の展開の前に \xx@temp が展開される必要が
    % あるので \expandafter を付けている
  \edef\xx@res@macro{\expandafter\xx@in@five@digits@a\xx@temp\xx@nil}%
}
% テスト
\xx@in@five@digits{123}
\typeout{\xx@res@macro} %==>00123
\xx@in@five@digits{\year}% \year は今年の年数を表す内部整数
\typeout{\xx@res@macro} %==>02012

ちなみに、上掲のコードの \xx@temp の部分だが、\the は引数に内部値しか取れないのに対して \number は引数に整数表記も取れる(つまり \number\year\number002012\number"7DC も可能で全て 2012 に展開される)ので、これを使うと変数代入が不要となり \def\xx@temp{\number#1} で済む。さらに、\xx@nil を展開不能にしておくと、最後の \edef 中の \xx@temp をその定義本体で置き換えられる。つまり、\edef 一つで定義本体を置き換えられるのである。

\let\xx@nil\relax % \xx@nil を展開不能にする
\def\xx@in@five@digits#1{%
  \edef\xx@res@macro{\expandafter\xx@in@five@digits@a
    \number#1\xx@nil}%
}

これの意味するところは、「\xx@in@five@digits は完全展開可能にできる」ということだが、これについては後で述べることにする。

正しい方法その 2: 戻り値を受け取る「変数」を引数に含める

先の方法が(初級者にとっては)解り易いと思うが、「特定の変数が関わるのが気持ち悪い」という場合は、「戻り値を受け取る変数」をマクロの引数に追加するという方法もある。*3つまり、\xx@remainder の規約を次のようにするのである。

  • \xx@remainder\CS{<整数1>}{<整数2>}
    〈整数1〉を〈整数2〉で割った余りを(引数で指定した)整数レジスタ \CS に代入する。

この規約に従った \xx@remainder のコードは以下のようになる。「その 1」と比べると、引数番号と代入先の変数が変更になるだけである。

\def\xx@remainder#1#2#3{%
  \xx@a=#2\relax
  \divide\xx@a#3\relax
  \multiply\xx@a-#3\relax
  \advance\xx@a#2\relax
  #1=\xx@a % #1で指定された変数に代入
}
%% テスト
\xx@remainder\@tempcnta{100}{42} % \@tempcnta に結果を返す
\typeout{\the\@tempcnta} %==>16

\xx@in@five@digits の方も同様の変更を加えるだけでできる。

\let\xx@nil\relax
\def\xx@in@five@digits#1#2{%
  \edef#1{\expandafter\xx@in@five@digits@a
    \number#2\xx@nil}%
}
%% テスト
\xx@in@five@digits\xx@year\year
\typeout{\xx@year} %==>02012

*1:「戻り値を特定の変数に代入する」というのは昔の BASIC でよく用いられた方法である。

*2:例えば、直後の文で 1 回参照されるだけである、等。

*3:関数引数の参照渡しが可能な言語では、(特に複数の「戻り値」を持たせたい場合に)引数の変数に結果を返すことがある。