最近書いた (La)TeX の闇コード

これは ████████ Advent Calender 3 日目の記事です.

秘伝のコードが動かない

弊ラボに伝わるコードの一部です:

\newcommand{\vA}{{\bm{A}}}
\newcommand{\vB}{{\bm{B}}}
\newcommand{\vC}{{\bm{C}}}
\newcommand{\X}{{\bm{X}}}
\newcommand{\Y}{{\bm{Y}}}
\newcommand{\Z}{{\bm{Z}}}
よく使うからというのはわからなくもないんだけど, 一貫性がないところが僕の感性からするとキモいな,とか, これ \bm だけでなく \mathcal とか \mathbb に対しても 同じような定義があって,そういあうたりにもやもやしながらも放置してたんですが, すでにわらわらパッケージを読み込んでいる Beamer ベースのファイルに このファイルを食わせたところ悲劇が起こり, 具体的には\x あたりが働かなくなってしまった.

どうせ全部の命令の頭に v つけるんなら \foreach あたりを使って なんかうまい具合にできるんじゃないか, と思っていろいろやった結果いい感じになった話です.

こうなった

\usepackage{pgffor}
\usepackage{bm}
\usepackage{amsmath,amssymb}

% Upper-case alphabet
\foreach \char in {A,...,Z}{
  % bold
  \expandafter\xdef\csname v\char\endcsname{%
    \noexpand\bm{\char}%
  }
  % cal
  \expandafter\xdef\csname c\char\endcsname{%
    \noexpand\mathcal{\char}%
  }
  % mathbb
  \expandafter\xdef\csname b\char\endcsname{%
    \noexpand\mathbb{\char}%
  }
}

これは何をしているのか

僕は TeX レベルの命令とかにあまり詳しくないので適当を言ってる可能性があります. 誤りの指摘などは歓迎します.

pgffor パッケージと \foreach に文字を渡す用法については PGF Manual の “83 Repeating Things: The Foreach Statement” (901 ページから)を読みましょう. とりあえずこう書くと \charA から Z と順々に定義されるんだ, と思ってくれればいいです.

ループのなかでは \vA とか \cA とか \bA みたいな命令を定義しています. 3 つの形は一緒なので,

  % bold
  \expandafter\xdef\csname v\char\endcsname{%
    \noexpand\bm{\char}%
  }
に絞って見ていきましょう.ここで \charA と定義されてることにします.

先に 1 行目から考えます.

  \expandafter\xdef\csname v\char\endcsname{%
……これをどれから説明していくべきかちょっと迷うんですけど, \csname / \endcsname から説明してみます.

\csname は対になる \endcsname までのトークン列を解釈して, 解釈した結果の文字列を名前とする命令に『展開』されます. 『展開』というのはまあ C とかでいうマクロ展開みたいな意味合いでの 『展開』だと思えばいいと思います.

\csname foo\endcsname
\foo
と解釈されることになります. また, \csname\endcsname までに含まれるマクロを展開するので,今回の例
\csname v\char\endcsname
\vA
となり,めでたく定義したい命令の制御綴を作ることができました.

\xdef はマクロを定義する命令 \def の仲間で, 具体的には \global\edef と等価です. まず \def

\def\foo{ABC}
のように,\def \macroname {replacement} の形で使います. 次に \edef{replacement} の中身に命令を展開した結果を置きたいときに 使います.今回話題にしている \char を使って説明すると,
\def\bar\char
\bar は以降それが置かれた場所で \char として解釈されることになりますが,
\edef\bar\char
とすると \barA と解釈されることになります.

\def / \edef の前に \global を置くと 定義した命令は文書全体で有効になります. \foreach ループの中にローカルなスコープがあるため, その外でも利用したい命令を定義するには \xdef (= \global\edef) を使う必要があります.

\expandafter はここまで説明したものに比べるとちょっとトリッキーな命令 (と僕は思う)ですが,ここまで挫折せずに読めてれば理解できると思います. 挫折してしまうのは僕の文章力が足りないせいかもしれないのにこんなこと言うのは自分の文章力に絶対の自信がありそうでヤバいなって思いました.

\expandafter は展開の順番を変える命令で, 『ひとつ後のマクロを展開する前に』ふたつ後のマクロを展開します.

  \expandafter\xdef\csname v\char\endcsname{%
    \noexpand\bm{\char}%
  }
の 1 行目は,
  \xdef\vA{%
と解釈してほしいわけですが,仮に 1 行目を
  \xdef\csname v\char\endcsname{%
と書いてしまうと, \csname の定義が置換されてすべてが破滅します.
  \expandafter\xdef\csname v\char\endcsname{%
のように \expandafter が前に置かれることで \csname ~ \endcsname が先に解釈されて,
  \xdef\vA{%
が得られることになります.

次に \xdef の中身,

    \noexpand\bm{\char}%
について考えます. \noexpandedef のなかだけど次にくるマクロの展開はしたくない, というときに使います.ここでなぜ \bm\mathcal\mathbb の展開を 抑止しなくてはならないのかはよくわからないんですが, 実際 \noexpand 外すとエラーで怒られるし,そういうもんなんだと思ってます. (知ってたら教えてほしい……僕が理解できるかみたいなところもあるけれど)

\edef によって \char は展開されるので,結果として

  \expandafter\xdef\csname v\char\endcsname{%
    \noexpand\bm{\char}%
  }
  \xdef\vA{%
    \bm{A}%
  }
と展開され,所望のマクロ定義が得られます.

まとめ

  • \foreach の便利(?)用法『文字をループ変数で扱える』の紹介
    • こんなメタな使い方でなくても $a_1$, $b_1$, みたいなのを 大量生産するみたいな用途にも便利だと思います
    • TikZ と組み合わせて各座標にノード置くとかもできる
  • \expandafter / \csname など LaTeX レベルだと触れないコマンドのさわりの紹介
    • スタイルファイルとかの TeX レベルのファイルにはよく出てきます
  • TeX 内で閉じるという気持ちよさはあるけど,適当な言語で A から Z までの太字コマンド量産するスクリプト書くほうが早かった説はある
    • まあ定義したいものの複雑さに応じてケースバイケースみたいな感じですよね
  • これを真面目に説明しているせいでゼミのスライド作りが終わらない
  • みんなの闇マクロ定義も教えてください
comments powered by Disqus