読者です 読者をやめる 読者になる 読者になる

アドベントカレンダー二日目 ― \newif\ifnextyear \ifnextyeartrue

TeX

TeX & LaTeX Advent Calendar TeX で騒げ、TeX で笑え
この記事は,12/2 担当分です.< 12/1 は ZR さん | | 12/3 はk16.shikano さん>

周りは \expandafter を叫んだりする記事みたいなので,私はマイナーな(?)プリミティブである \aftergroup と LISP on TeX での利用法を一例としてあげようと思う.

そもそも \aftergroup ってなによ

黄色(LaTeX2e マクロ & プラス プログラミング基礎解説)*1にも載っていない*2が,TeX の強力なトークン展開の制御機能ひとつである.\aftergroup は現在のグループが終わった地点での動作をフックできる.使い方は,

\aftergroup<トークン>

で,トークンがグループ終了時に挿入される.複数指定したときは,指定した順序でそのすべてのトークンが挿入される.

これだけでは直感に欠けるので,コード例を.

{\aftergroup\show}\expandafter

実行結果は次のようになる.

$ tex test.tex
This is TeX, Version 3.1415926 (TeX Live 2012/dev/W32TeX)
 encTeX v. Jun. 2004, reencoding enabled.
(./test.tex
> \expandafter=\expandafter.
l.1 {\aftergroup\show}\expandafter

? x
No pages of output.
Transcript written on test.log.

グループの終わり,すなわち \expandafter の前に \show が挿入され,\show\expandafter となりこのような表示がなされる.

で,なにに使うの?

私も最初はよくわからなかった.しかし,TeX で言語処理系を実装するよくある状況*3では非常に有用なことがわかった.末尾呼び出しの最適化である.

末尾呼び出しの最適化とは?

末尾呼び出しの最適化とは,再帰呼び出しを安心してループのように使うために必要な最適化技術である.次の Scheme プログラムを例にして説明する.

(define (fact n acc) (if (= n 0) acc (fact (- n 1) (* acc n))))
(fact 10 1)

階乗を計算する末尾再帰になっているプログラムである.最適化しないナイーブな実装では,関数の呼び出しスタックは

[(fact 10 1)][(fact 9 10)][(fact 8 90)][(fact 7 720)].........[(fact 0 3628800)]

となる.ここで fact 関数の末尾での動作は,呼び出した関数の結果を返すだけというものであり,わざわざスタックを消費する必要がない.そんなときを検知して,スタックを潰すのが末尾呼び出しの最適化である.

で,\aftergroup との関連性は?

TeXインタプリタを作成する場合,この呼び出しスタックをグループで表現するのがよくあるデザインである.グループのネストには限界があるので,末尾呼び出しの最適化は重要な課題となる*4.ここで,\aftergroup である.\aftergroup で「次にやるべき計算」*5をグループが終わったあとに挿入させるようにし,現在のグループを閉じてしまえば,不必要な要素をたたんで,新たな要素をスタックに積むという動作を実現することができる.

具体的に,LISP on Tex では \@@tco という名前のトークンに「次にやるべき計算」を束縛し,\aftergroup\@@tco とすることで末尾呼び出しを最適化している.

おまけ -- TeX におけるフック機能について

TeX には \aftergroup 以外にも,様々な場所をフックできるキチガイ言語である.一覧にしてみよう.

\output 出力ルーチンそのもの*6
\par 段落の間に挿入される.これを書き換えると,段落の終わりをフックできる.しかし,オリジナルの \par を実行しないと段落が変わらないため,初心者にはおすすめしない
\everypar 段落の先頭に挿入されるトークンリスト.行頭に行番号を入れるなどのマクロは,これを使うと実装できる.
\everymath 文章内の数式をはじめる時のフック.
\everydisplay ディスプレイ数式のフック.
\everyvbox 垂直ボックスの開始.
\everyhbox 水平ボックスの開始.
\everycr 表組みでの \cr の後ろ.TeX の表組みプリミティブである \halign と \valign を知ってないと使いづらいかもしれない.
\afterassignment レジスタへの代入やマクロ定義のフック.

*1:TeX プログラミングするときの有名な参考書のひとつ.絶版

*2:索引見て確認した

*3:BASIC とか Grass とか LISP とか

*4:LISP on TeX の初期実装では,処理系が簡単に落ちたという悲惨な歴史もあったりする.

*5:継続みたいなもの.

*6:フック -> 修正