マクロツイーター

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

TeX は軽量プログラミング言語(LL)か?

という話が(会社で)持ち上がったことがある(いや、本当に)。

LL のイベントで TeX がネタになったことがあることは知っているが、TeX が LL であるという話は聞いたことがない。ちょっと考えてみる。

Wikipedia によると:

LLという単語の推進者は、
などの特色を備えるものをLLと分類している

なるほど。とりあえずこれらの性質について考えてみる。

【事前にコンパイルが不要なインタプリタ

TeX は非常に純粋なインタプリタ言語である。最近のスクリプト言語の処理系は、ほとんどが中間言語へのコンパイルを介して実行されている。それに対して、TeX はそういう手法が原理的に使えない言語である。なぜなら、「今までの出力が 20 ページ以上あるならここから『a』の文字を(構文解析上)全部無視する」とかの恐ろしいプログラムが書けるからである。*1

【動的型】

多分、型付けが動的か静的かというときに一番注目されるのが「変数に型があるか」だろう。TeX で「変数」に一番近い概念は「レジスタ」だが、これには型の区別がある。

\newcount\foosize    % 整数型の変数を宣言
\newdimen\barwidth   % 寸法型の変数を宣言
\foosize=42
\barwidth=5cm
\foosize=5cm         % 「\foosize=5」実行後「cm」を出力
\barwidth=42         % エラー
\relax
\barwidth=10pt
\foosize=\barwidth   % キャストが行われる

変数(レジスタ)への代入は変数の型の値が記述されていることを期待するので、そうでない場合は予定外の動作が起こる。\barwidth=42 は次に「pt」「cm」等の「単位」があるべきだが、ない(\relax である)のでエラーになる。((もしここで \relax がないと、次の行の \barwidth を「単位」とみなして代入文「\barwidth=42\barwidth」を実行する。))式の形によっては、キャスト(暗黙の型変換)が行われ、例えば最後の代入文では \foosize に「\barwidth を sp 単位で表した整数値」―― 1pt = 65536sp なのでこの場合は 655360 ――が代入される。*2この点の振舞は静的型付けの言語に近い(ただし完全にインタプリタ動作なので型エラーは実行時にしか起こらない)。

次に関数の入出力の型を考えてみると……そもそも TeX には「関数」という概念がない。「関数」に最も類似するのは「マクロ」だろうが、マクロは「値を受け取る」のではなく「トークン列を操作する」だけであり、そして、全ての値はトークン列として表現される。だから、強いて言えば、マクロの引数に関して「そのトークン列の表す値の型」が意識されることはないといえる。((あと、制御綴のトークンは何らかの対象を指している(あるいは何も指してない、つまり未定義)が、その対象の種類に対する制限はない。つまり、\foo は文字(chardef)、整数レジスタ、寸法レジスタ、マクロ、他種類の対象、未定義の何れでもありえるし、実行中に種類が変わることもある。))これは動的ということ?

【関数オブジェクト】

……だから TeX には「関数」がない。以上。

……でもよいが、一応マクロについて考えておく。マクロも「他の種類の対象」と同じくトークン列で表されるという性質は、関数が「他の種類(数値や文字列)の対象」と同じく「値」として扱えると性質に類似している。例えば、次に示したようなコード(LaTeX フォーマットを前提とする)は、「手続型言語の関数オブジェクトの利用方法」に似てるような気もする。*3

% #1 のテキスト修飾命令を施して「Hello」を出力する
\def\printHelloIn#1{%
  #1{Hello}%
}
\printHelloIn\textbf     % \textbf{Hello} と同じ
\printHelloIn\underline  % \underline{Hello} と同じ
%
% #2 と #3 の修飾命令を合成したものを #1 として定義する
\def\defineCompose#1#2#3{%
  \def#1##1{#2{#3{##1}}}%
}
\defineCompose\textbfit\textbf\textit
\textbfit{Wow!}  % \textbf{\textit{Wow!}} と同じ
%
% \sqrt に自動的に \mathstrut が入るようにする
\let\orgSqrt\sqrt   % まず元の命令を別名にコピー
\def\sqrt#1{\orgSqrt{#1\mathstrut}}  % それを用いて再定義
$\sqrt{g}+\sqrt{h}$

【結論】

……つまりは、ふつうのプログラミング言語の類型論を TeX に当てはめるのが無理のようである。

しかし、実際のところ、日本における LL の定義は「LL だと主張すれば LL」みたいなところがある。じゃあ、私は TeX を LL であると主張したいかというと、とてもその気にはなれない。 周知の通り、(日本の)LL の「軽量」は「プログラマの負荷」についてである。そうすると、例えば、「学習のコストが低い」「プログラムの設計・実装・修正が容易である」といった特質の方が、言語自体の外形的な類型論よりも本質的であるように思える。では TeX 言語がこれらの性質を満たすかというと……そうは思えないのである。というのが私の考えである。


TeX は軽量でないことになってしまったが、同時にこのことは (La)TeX を使用する上での重要な教訓を示している。それは「最終的に TeX で出力するからといって、全ての処理を TeX で実装する必然性はない」ということである。TeX の入力はプレーンテキストなので、本当に軽量な言語(PerlRubyLua、…)を用いた処理と相性がよい。なので、TeX よりも軽量な(自身の負担が少ない)言語を組み合わせるのは寧ろ合理的である。

例えば、2011-01-09 の中で、「CSV 形式の住所録」を入力として、年賀状の宛名書きを TeX(XeLaTeX)で行うという話をしている。そこでは、版面レイアウト作成処理の部分を TeX 言語で実装して LaTeX パッケージとし、CSV からの入力を「そのパッケージを用いた LaTeX 文書」にする処理を Perl で実装している。CSV からの入力などのレイアウト以外の処理を TeX 言語で実装することは可能であるが、それを行おうとは考えなかった。理由は、「Perl で書いた方が圧倒的に早く書ける」からである。そもそも時間のない状況だったので、一番早く実装が終わるように、使用言語を分けたのである。LaTeX 上の処理に関して、少々複雑な TeX マクロを書くのに手間取っている*4人には、所望の処理が別の「軽量な言語」との組み合わせで実現できないかを一度検討することを勧める。*5

最後に一言。「TeX エンジン上で軽量な言語が動く世界」はもっと刺激的で素晴らしいものであろう!

*1:ところで、TeX プログラム(文書)を TeX 処理系に与えて DVI ファイルに変換することを「コンパイル」と呼ぶ人がいる。「自分が書いたテキスト形式のソースと同じ内容をもった」バイナリができるという点でコンパイラ言語の処理系と類似しているからであろう。しかし実際には DVI ファイルに書かれているのは TeX コードの「実行結果」であり、TeX 言語のコードを形式変換したものではない。従って、この「コンパイル」は実際には「インタプリタの実行」である。

*2:つまり、整数を読むべき時に「内部寸法値」があった場合は寸法から整数へのキャストが起こる。

*3:「静的束縛を伴うクロージャ」に相当する概念はないだろう。

*4:多くの場合「TeX 言語がなかなか身につかない」のだと思う。

*5:「ソースファイルの加工」という処理に落とし込むことができれば、大抵の言語(PerlRubyLua、…)で処理が可能である。