expl3 / LaTeX3 プログラミング層

expl3(エクスプ・スリー)は、現代の LaTeX を内側で支えるプログラミング言語です。かつて「LaTeX3」と呼ばれた取り組みから生まれ、いまでは LaTeX カーネルに標準で読み込まれています。\newcommand で書ける範囲を超えた、変数・データ構造・整数演算・浮動小数点までそなえた本格的な層で、xparse・siunitx・fontspec などはこの上に作られています。このページでは構文の切り替え方、独特の命名規則、そして主要なモジュールを順に見ていきます。

expl3 とは何か

TeX は本来マクロプロセッサで、\def\newcommand で命令(マクロ)を定義していきます。しかし大きなパッケージを書こうとすると、TeX の生のプリミティブは一貫性に欠け、引数の展開や変数の扱いが職人芸になりがちでした。expl3 は、TeX と e-TeX のプリミティブに新しい名前を与え、関数と変数を体系的に名づけ、引数の型を明示する一貫したインターフェースを用意します。LaTeX Project が長年かけて整備した、いわば「LaTeX のための標準ライブラリ兼プログラミング言語」です。

実用上の重要な事実が一つあります。新しい LaTeX カーネルでは expl3 が標準で読み込まれている こと。公式の interface3 マニュアルにも「最新の LaTeX 2ε カーネルでは、この素材はフォーマットの一部として読み込まれる」と明記されています。したがって \usepackage{expl3} を書く必要はもうありません。多くの利用者は expl3 を直接書かず、xparse\NewDocumentCommand)や siunitx・fontspec・l3keys2e といったパッケージ越しに間接的に使っています。

構文を切り替える — \ExplSyntaxOn

expl3 のコードは \ExplSyntaxOn\ExplSyntaxOff で囲んだ区間に書きます。この区間では、文字の扱い(カテゴリコード)が二つ変わります。第一に、空白と改行が無視される こと。おかげでコードをインデントし、トークンの間に空きを入れて、読みやすく書けます。第二に、**_(アンダースコア)と :(コロン)が「文字」として扱われる** こと。ふつうの LaTeX ではこれらは命令名に使えませんが、expl3 ではこれらを含んだ長い命令名を作れます。

空白が無視されるということは、出力に 本物の空白を入れたいときは別の書き方が要る ということでもあります。expl3 区間では、半角スペースを ~(チルダ)で表します。たとえば本文に「Item: りんご」と出したいなら、Item:~#1 のように書きます(素の空白は消えてしまうため)。この一点さえ押さえれば、あとは普通のプログラミング言語のように読み書きできます。

document.tex
% expl3 はカーネルに含まれるので \usepackage{expl3} は不要
\ExplSyntaxOn
  % ここでは空白・改行が無視され、 _ と : が命令名に使える
  \tl_new:N  \l_greeting_tl
  \tl_set:Nn \l_greeting_tl { Hello,~world! }
  \tl_use:N  \l_greeting_tl
\ExplSyntaxOff

命名規則と引数シグネチャ

expl3 の関数名は、\⟨module⟩_⟨description⟩:⟨arg-signature⟩ という形をしています。最初の _ までが モジュール名(そのデータ型や機能の分類)、: の手前までが 説明的な名前、そして : の後ろが 引数シグネチャ(arg-spec)です。たとえば \seq_put_right:Nn なら、モジュールは seq(シーケンス)、説明は put_right(右端に追加)、シグネチャは Nn。シグネチャの各文字は、その位置の引数を 渡す前にどう処理するか を表します。

主な引数指定子は次のとおりです(公式 interface3 マニュアルの記述に基づきます)。Nn がもっとも基本で、これだけでもかなりのことができます。展開を伴う xeof や、変数の値を取り出す Vv は、慣れてから少しずつ覚えれば十分です。

指定子意味
N加工しない、単一トークン(多くは制御綴 1 個)。
n加工しない、波括弧でくくったトークン列
c中身を **\csname で命令名に変換** してから渡す。
V / v変数の値を取り出して渡す(V は単一トークン、v は命令名から構築)。
o一度だけ展開してから渡す。
x / e完全展開x\edef 相当・展開不可、e\expanded 相当)。
f最初の展開不可トークンまで 展開(先頭から左→右に)。
pTeX の パラメータテキスト#1#2…)。関数定義で使う。

変数も同様に名づけますが、先頭に スコープを表す 1 文字 が付きます。l_局所(現在の TeX グループ内だけで変える)、g_大域(グローバルに変える)、c_定数(値を変えない)です。変数名の末尾には型を表す識別子が付き、_tl(トークンリスト)、_int(整数)、_seq(シーケンス)、_prop(プロパティリスト)、_clist(コンマリスト)、_fp(浮動小数点)などになります。たとえば \l_my_name_tl は「局所のトークンリスト変数」、\g_counter_int は「大域の整数変数」と一目で分かります。なお各モジュールは \l_tmpa_tl\l_tmpb_int のような スクラッチ変数(使い捨ての一時変数)も用意しています。

主要なモジュール

expl3 はデータ型ごとにモジュールへ分かれており、どれも「作る・設定する・使う」という同じリズムで操作できます。まず 関数の定義 から。\cs_new:Npn は新しい関数を定義し、すでに同名の関数があればエラーにします(cs は control sequence の意)。\cs_set:Npn は同じく定義しますが、現在の TeX グループ内に限定され、再定義してもエラーになりません。どちらも :Npn で、N=定義する関数名、p=パラメータテキスト(#1#2…)、n=本体(置換テキスト)を表します。

次に **トークンリスト(tl)**。これは文字列のように使える、最も基本的な変数です。\tl_new:N で宣言し、\tl_set:Nn で中身を入れ替え(以前の内容は消えます)、\tl_use:N で本文に出力します。**整数(int)** も同じ形です。\int_new:N で宣言、\int_set:Nn で代入し、\int_eval:n は中の整数式を計算してその値を返します(たとえば \int_eval:n { 2 + 3 * 4 } は 14)。

**シーケンス(seq)** は、両端から出し入れできるリスト(スタックにも使える)です。\seq_new:N で作り、\seq_put_right:Nn で右端に要素を追加し、\seq_map_inline:Nn で全要素を順に処理します。後者では、各要素が #1 として渡されます。**プロパティリスト(prop)** は、いわゆる辞書(キーと値の対応表)です。\prop_new:N で作り、\prop_put:Nnn でキーと値の組を格納します(Nnn は「変数・キー・値」の三引数)。

**コンマリスト(clist)** は、その名のとおりコンマ区切りの値の並びを扱うモジュールで、命令はすべて \clist_ で始まります(\clist_new:N\clist_set:Nn など)。最後に **浮動小数点(fp)**。\fp_eval:n は小数を含む式を評価し、sinsqrtpi など多くの科学関数も使えます(たとえば \fp_eval:n { sqrt(2) }\fp_eval:n { 2 * pi })。整数しか扱えない \int_eval:n との使い分けが要点です。

小さな例 — 関数とシーケンス

以上を組み合わせてみます。次の例は、\cs_new:Npn で「シーケンスに項目を一つ加える」関数 \example_add:n を定義し、それを何度か呼んでから、\seq_map_inline:Nn で全項目を箇条書き風に出力します。命令名に _: が含まれていても、\ExplSyntaxOn 区間なので問題なく書ける点に注目してください。

document.tex
\documentclass{article}
\begin{document}
\ExplSyntaxOn
  % シーケンスを 1 個用意する
  \seq_new:N \l_example_fruits_seq

  % 「右端に 1 項目追加する」関数を定義する
  \cs_new:Npn \example_add:n #1
    { \seq_put_right:Nn \l_example_fruits_seq {#1} }

  % 何度か呼ぶ
  \example_add:n { apple }
  \example_add:n { banana }
  \example_add:n { cherry }

  % 全項目を出力する(各項目が #1 に入る。 ~ は空白)
  \seq_map_inline:Nn \l_example_fruits_seq
    { Fruit:~#1 \par }
\ExplSyntaxOff
\end{document}

これをコンパイルすると、「Fruit: apple」「Fruit: banana」「Fruit: cherry」が 3 行に分かれて出ます。\cs_new:Npn#1 は定義する関数の引数、\seq_map_inline:Nn#1 は走査中の各要素で、どちらも n 型(波括弧の中身)として扱われています。Fruit:~#1~ を素の空白にすると、空白は無視されて「Fruit:apple」とくっついてしまう点に注意してください。

実際の使われ方

expl3 を直接書く必要は、ふつうの文書執筆ではほとんどありません。けれども パッケージやクラスを作る 段になると、expl3 は今や事実上の標準です。利用者向けの命令を \NewDocumentCommand(xparse)で受け取り、その中身を expl3 で実装する、という組み合わせがよく使われます。xparse の引数指定(mO{...}s など「文書レベル」の引数)と、expl3 の引数シグネチャ(Nn など「プログラミングレベル」の処理指定)は別のものなので、混同しないようにしましょう。前者は xparse のページで詳しく扱います。

expl3 を学ぶと、\newcommand だけでは難しかった処理が見通しよく書けるようになります。覚えておきたい要点をまとめます。

  • コードは必ず \ExplSyntaxOn\ExplSyntaxOff で囲む。区間内では空白は無視され、空白を出したいときは ~ を使う。
  • カーネルに含まれるので \usepackage{expl3} は不要。.sty の中ではパッケージ宣言の後にそのまま書ける。
  • 関数は \⟨module⟩_⟨description⟩:⟨signature⟩、変数は \⟨scope⟩_⟨name⟩_⟨type⟩。スコープは l_g_c_
  • 確信のない命令名は使わない。命名は厳密で、公式の interface3 マニュアル(texdoc interface3)が一次資料。