マクロツイーター

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

それでも TeX でプログラミングしたい人のための何か (8)

あるいは 〜私の TeX プログラム変換環境〜

かなり間が空いたが、前回の続き……だけどその前に宣伝。

TeXLaTeX Advent Calendar

TeX で騒げ、TeX で笑え

皆さんの、心をこめたネタをお待ちしております!
※初心者大歓迎。 いやマジで。

(詳細は ATND のページで)

ステップ 5 : 配列変数の処理

元のプログラムはシリーズ(7)の最後に掲載した eltaso3.lua

元のプログラムには alpha と digit という 2 つの配列変数を用いている。*1残念ながら、TeX には配列という機能はない((ちなみに、TeX において「配列」というと、普通はデータ型でなくて組版機能の alignment のこと(\halign 等)を指す。))ので、何か別の手段でその機能を実現する必要がある。ここでは「名前参照」を用いて擬似的な配列変数を作る方法を説明する。

「名前参照」という機能

名前参照というのは、「文字列データを用いて、その文字列と同じ名前をもつ変数を参照すること」であるが、言葉では解り難いので、Perl 言語を例にとって説明する。

Perl の文法では「普通の」変数(スカラー変数)は識別子(変数名)の前に $ を付けたもので表される。

$foo = 42;
$name = "foo";
print("The value of ", $name, " is ", $foo, ".\n");
  #==>「The value of foo is 42.」と出力

ところで、上のコードでは、変数 $name には「foo」という文字列値が入っている。この「値」を利用して変数 $foo にアクセスすることはできるだろうか。Perl ではそれが可能で、${<値>} という表記を使って、その「値」の文字列に一致する名前をもつ変数にアクセスができる。これが「名前参照」である。 ((なお、「${$name}」のように中が単純な変数の場合は「$$name」と略記することができる。PHP でもこの形式で名前参照ができる。))

print(${$name}, "\n"); #==>「42」と出力
print(${"foo"}, "\n"); #==>「42」と出力
print(${substr("foolish", 0, 3)}, "\n"); #==>「42」と出力

そして、この名前参照の機能を用いると、本物の配列変数*2を用いることなく、「擬似的な配列」の機能を実現することができる。例えば、「foo/1」「foo/2」……のような一連の名前の変数を用意し、これを「配列 foo」だと見做せばよい。

# 4つの整数の合計を求める
${"value/1"} = 1;
${"value/2"} = 8;
${"value/3"} = 27;
${"value/4"} = 64;
$sum = 0;
for $k (1 .. 4) {
  $sum += ${"value/".$k};
}
print($sum, "\n"); #==> 100

名前参照はどの言語でも可能なわけではない。例えば、C 言語のように機械語コンパイルされる言語においては、変換後の(機械語の)プログラムではもはや「変数名」という概念が存在しない*3ので名前参照は不可能である。*4

TeX で「名前参照」を用いて配列を実現する

TeX(on LaTeX)では、\@nameuse\@namedef という命令を用いて「名前参照」を行うことができる。

\def\xx@foo{FOO}
% ↓は \typeout{\xx@foo} と同じ
\typeout{\@nameuse{xx@foo}} %==>「FOO」を表示

% ↓は \def\xx@bar#1{BAR(#1)} と同じ
\@namedef{xx@bar}#1{BAR(#1)}
\typeout{\xx@bar{!?}} %==>「BAR(!?)」を表示

従って、普通の言語での配列に相当する機能を、TeX では名前参照を用いて代替することができる。先に示した Perl の「擬似配列」のプログラムを TeX に直すと以下のようになる。

\documentclass[a4paper]{article}
\makeatletter %!!!!!!!!!!!!!!!!!!!!!!!!!
% 4つの整数の合計を求める
\@namedef{xx@value/1}{1}
\@namedef{xx@value/2}{8}
\@namedef{xx@value/3}{27}
\@namedef{xx@value/4}{64}
\newcount\xx@sum
\newcount\xx@k
\xx@sum=0 \xx@k=0
\@whilenum{\xx@k<4}\do{%
  \advance\xx@k 1
  \advance\xx@sum\@nameuse{xx@value/\the\xx@k}\relax
}
\typeout{\the\xx@sum}%==>「100」を表示
\makeatother  %!!!!!!!!!!!!!!!!!!!!!!!!!
\begin{document}
\end{document}

ここで幾つか注意すべき点がある。

  • 配列の添字は数値であるが、名前参照で用いる「変数名」は文字列値であるので、数値から文字列への変換が必要である。Perl では "value/".$k((「.」は文字列同士の連接の演算子(だから引数の文字列への強制が起こる)。なお実際の Perl ではこういう式は普通は文字列展開を利用して "value/$k" と書く。))を求める際に暗黙の変換が行われている。対して TeX では \the で明示的に文字列に変換することになる(xx@value/\the\xx@k)。
  • \@namedef は「\def の代わり」であってマクロを定義する。つまり「変数」としては数値ではなく文字列変数を定義する。これだけの知識で実装するために、本来数値の配列である value を文字列の配列として扱った。本当は、TeX で「整数の擬似配列」を作ることも可能ではあるが、現実の TeX プログラミングでは、擬似配列はマクロ(変数としては文字列)に対して用いられることがほとんどで、その場合、数値も文字列として扱われる。その理由は、整数の変数の実体である「整数レジスタ」が昔の TeX では 256 個しか用意されておらず、*5そもそも整数の大きな配列を作ることができなかったからであろう。
  • なお、TeX では、構文解析上で整数が求められるところに数字列が与えられるとそれは自動的に整数値と解釈される。(\advance\xx@k 1 の「1」は整数 1 と解釈する。当たり前。)従って、「整数を表す文字列」の変数はそのまま整数として扱える、つまり上の \@nameuse{xx@value/\the\xx@k} は結局は整数として解釈される。
  • xx@value/1」という変数の「値」を期待している所では \@nameuse{xx@value/1} と書いてよいが、「xx@value/1」という「変数そのもの」を表したい場合は \@nameuse{xx@value/1} は使えない。例えば、「xx@value/1」の値を設定したい場合は「\def\@nameuse{xx@value/1}{1}」とは書けず((これだと \@nameuse という命令の再定義だと見做されてしまう。))、\@namedef という「専用の命令」が必要となる。((すなわち、\def 以外では \edef\let と共には \@nameuse が使えない。なお、名前参照のための最も基本的な命令(TeX のプリミティブ)が \csname\endcsname であり、\@nameuse\@namedef はこのプリミティブを用いて実装されている。「\@namedef\edef 版」とかを同様に実装することも可能である。))

*1:実際には配列変数という概念が Lua にあるわけでなくて、多くのスクリプト言語と同様、単に「配列オブジェクト」を値とする変数に過ぎないのであるが。

*2:もちろん、Perl には本物の配列の機能がある。そして「配列専用の変数」という概念も存在する。

*3:単なるアドレスに変換されてしまっている。

*4:変数名の「文字列」に依存した操作を含まず、単に変数の可変な参照を行いたいことが目的であれば、変数へのポインタや「本物の参照機能」がその役割を果たせる。C言語ではポインタが使える。Perl5 では変数への「参照」を得る機能があるので、「名前参照」の使用は推奨されない。(strict モードでは禁止される。)

*5:おまけに、1つのプログラムが大量にレジスタを確保してしまうと他のプログラムが使えなくなるので、昔は「整数レジスタは可能な限り節約する」ことが求められていた。現在の LaTeX の実行に使われる pdfTeX エンジンでは 32768 個のレジスタが使えるのでこの問題はなくなっているが、習慣的に、整数の配列は用いられていないままである。