expl3 / LaTeX3 プログラミング層

\usepackage{expl3} と書いても、いまの LaTeX では何も読み込まれません。 expl3 —— かつて「LaTeX3」と呼ばれた取り組みから生まれた LaTeX のプログラミング層 —— は、すでにフォーマットの一部だからです。実際、カーネルのソース latex.ltx の 1125 行目には Skipping: expl3 code already part of the format というログ出力があり、expl3.sty は 53 行目で「もう入っているか」を確かめて、入っていればコードの読み込みを丸ごと捨てます。このページでは、\ExplSyntaxOn が文字の扱いをどう変えるのかを実測し、\module_function:nn という独特の名前の読み方、引数指定子、そして tlseqpropintfp といったデータ型を順に見ていきます。

expl3 はすでにカーネルの中にある — \fpeval はその証拠

TeX はもともとマクロプロセッサで、\def\newcommand で命令を定義していく道具です。しかし大きなパッケージを書こうとすると、生のプリミティブは一貫性を欠き、展開の制御や変数の扱いが職人芸になりました。expl3 は、TeX と e-TeX のプリミティブに新しい名前を与え、関数と変数を体系的に名づけ、引数の型を名前そのものに書き込む一貫したインターフェースです。LaTeX Project が長年かけて整備した、いわば LaTeX のための標準ライブラリ兼プログラミング言語で、xparse・siunitx・fontspec・l3keys2e はみなこの上に建っています。

そして、その層はもう別売りではありません。expl3.sty を開くと、53 行目から 59 行目にかけて \tex_let:D がすでに定義されているかを調べ、定義済みなら \input expl3-code.tex\@gobble丸ごと食べてしまうという数行が見つかります。パッケージとして残っているのは名前だけの入れ物(自己申告も L3 programming layer (loader) です)で、中身はフォーマットに入っています。実際、何も読み込まない素の article でも \ExplSyntaxOn\tl_new:N\ProvidesExplPackage もすべて定義済みです。いちばん分かりやすい証拠は latex.ltx の 1167 行目からの 4 行——\fpeval\inteval\dimeval\skipeval\cs_new_eq:NN\fp_eval:n\int_eval:n\dim_eval:n\skip_eval:n別名として定義されていることです。つまり文書レベルの \fpeval{sqrt(2)} は、expl3 の関数そのものを別の名前で呼んでいるだけです(カウンタと長さのページを参照)。

\ExplSyntaxOn が実際に変えるもの — 空白と ~ の交代

expl3 のコードは \ExplSyntaxOn\ExplSyntaxOff で囲んだ区間に書きます。この区間で変わるのは 4 文字のカテゴリコードだけ です。カーネルに \the\catcode を吐かせて前後を比べると、こうなります——空白は 10(空白)から 9(無視される文字)へ_ は 8(下付き)から 11(文字)へ: は 12(その他)から 11(文字)へ、そして ~ は 13(アクティブ)から 10(空白)へ\ExplSyntaxOff を書くと 10・8・12・13 にきちんと戻ります。カテゴリコードそのものはカテゴリコードのページが詳しいですが、ここで面白いのは 空白と ~ が席を交換している ことです。空白はカテゴリコード 10 を明け渡して「無視される文字」の 9 番へ移り、空いた 10 番に ~ が座る。だから expl3 の中では、~ は「空白を出すコマンド」ではなく文字どおり空白そのものなのです。

文字通常の LaTeXExplSyntaxOn の区間
(space)10 — 空白9 — 無視される文字。 インデントも改行も出力に影響しない
~13 — アクティブ(非改行空白)10 — 空白そのもの。 出力に空きを入れたいときはこれを書く
_8 — 下付き添字11 — 文字。 命令名の中に書ける
:12 — その他の文字11 — 文字。 引数シグネチャの区切りに使える

この交代の実務上の意味は一つです。expl3 の中で素の空白を書いても出力には出ません。 「Fruit: apple」と組みたければ Fruit:~#1 と書く必要があり、~ を素の空白に替えると「Fruit:apple」とくっついてしまいます。裏を返せば、コードを好きなだけインデントし、トークンのあいだに空きを入れて読みやすく書いても、出力は一切変わらないということです。ふつうの LaTeX が空白に神経を使う言語であることを思えば、これはかなりの解放です。

document.tex
% no \usepackage{expl3} is needed: it is in the format
\ExplSyntaxOn
  % spaces and newlines here are catcode 9 (ignored); _ and : are letters
  \tl_new:N  \l_greeting_tl
  \tl_set:Nn \l_greeting_tl { Hello,~world! }   % ~ is the real space
  \tl_use:N  \l_greeting_tl
\ExplSyntaxOff

\seq_put_right:Nn の読み方 — 名前が型を語る

expl3 の関数名は \⟨module⟩_⟨description⟩:⟨arg-signature⟩ という形をしています。最初の _ までが モジュール名(データ型や機能の分類)、: の手前までが 説明的な名前: の後ろが 引数シグネチャです。\seq_put_right:Nn なら、モジュールは seq(シーケンス)、説明は put_right(右端に追加)、シグネチャは Nn。シグネチャの各文字は、その位置の引数を 渡す前にどう処理するか を表します。つまり名前を読めば、何個の引数を取り、それぞれをどう扱うかが分かる——ドキュメントを引かなくても型が読み取れる、というのがこの命名規則の狙いです。

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

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

:Nn:Nx の違いを目で見る

n は書いたものをそのまま、x は「いま」の値を焼き付けて格納します。 抽象的に説明するより、\tl_show:N で中身をログに吐かせるのが早い。同じ { [ \l_src_tl ] }\tl_set:Nn\tl_set:Nx で 2 つの変数に入れると、ログにはこう出ます——> \l_a_tl=[\l_src_tl ].> \l_b_tl=[FIRST].n 版は \l_src_tl というトークンをそのまま持ち、x 版は代入時点の値 FIRST に置き換わっています。この後で \l_src_tlSECOND に変えて両方を出力すると、n 版は「[SECOND]」、x 版は「[FIRST]」。つまり n は参照を、x はスナップショットを保存するわけです。展開の制御が難しいと言われる場面のほとんどは、この違いの選び間違いです。\tl_show:N は詰まったときの最初の一手として覚えておく価値があります。

document.tex
\ExplSyntaxOn
  \tl_new:N \l_src_tl  \tl_set:Nn \l_src_tl { FIRST }
  \tl_new:N \l_a_tl    \tl_set:Nn \l_a_tl { [ \l_src_tl ] }   % reference
  \tl_new:N \l_b_tl    \tl_set:Nx \l_b_tl { [ \l_src_tl ] }   % snapshot

  \tl_show:N \l_a_tl   % log: > \l_a_tl=[\l_src_tl ].
  \tl_show:N \l_b_tl   % log: > \l_b_tl=[FIRST].

  \tl_set:Nn \l_src_tl { SECOND }
  Nn~stored:~\tl_use:N \l_a_tl \par   % prints [SECOND]
  Nx~stored:~\tl_use:N \l_b_tl \par   % prints [FIRST]
\ExplSyntaxOff

データ型 — 作る・設定する・使う

expl3 はデータ型ごとにモジュールへ分かれており、どれも「作る・設定する・使う」という同じリズムで操作できます。\⟨type⟩_new:N で宣言し、\⟨type⟩_set:Nn で値を入れ、\⟨type⟩_use:N などで取り出す。型が変わっても手つきが変わらないので、一つ覚えれば残りは推測が効きます。関数の定義だけは少し形が違って、\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\tl_show:N
str文字列。トークンではなく文字として比較したいとき\str_if_eq:nnTF { a~b } { a~b }same を返す
int整数。四則と剰余の整数演算\int_eval:n { 2 + 3 * 4 }14\inteval はこの別名)
fp浮動小数点。sinsqrtpi など科学関数も使える\fp_eval:n { sqrt(2) }1.414213562373095\fp_eval:n { 2 * pi }6.283185307179586
seqシーケンス。両端から出し入れできるリスト(スタックにも使える)\seq_put_right:Nn で追加、\seq_map_inline:Nn で全要素を #1 として走査、\seq_use:Nn で区切り文字つきに連結
propプロパティリスト。いわゆる辞書(キーと値の対応表)\prop_put:Nnn で「変数・キー・値」を格納、\prop_item:Nn で取り出す
clistコンマ区切りの値の並び\clist_set:Nn \l_c_clist { 1,2,3 } のあと \clist_use:Nn \l_c_clist { ~+~ } は「1 + 2 + 3」
bool真偽値。\newif の代わりに使う\bool_new:N\bool_set_true:N\bool_if:NTF で分岐する
document.tex
\documentclass{article}
\begin{document}
\ExplSyntaxOn
  \seq_new:N \l_example_fruits_seq

  % define a function that appends one item
  \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 }

  % each item arrives as #1; ~ is a real space
  \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 型(波括弧の中身)として扱われます。命令名に _: が入っていても平気なのは、\ExplSyntaxOn の区間だからです。

l3keys と、パッケージを書くときの使い方

key = value 形式のオプションを自分の命令やパッケージに持たせたいときの標準が l3keys です。\keys_define:nn { module } { ... } でキーを宣言し、\keys_set:nn { module } { key = value } で設定します。キーの宣言には .tl_set:N(トークンリスト変数に入れる)、.bool_set:N(真偽値にする)、.code:n(任意のコードを走らせる)、.initial:n(既定値)といった プロパティ を後置します。この仕組みはもともと l3keys2e というパッケージ経由でパッケージオプションに繋がれていましたが、その中核は現在 LaTeX2ε カーネルに取り込まれ、\DeclareKeys\ProcessKeyOptions としてパッケージなしで使えます(パッケージ作成のページで実際に書いています)。

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

  • コードは \ExplSyntaxOn\ExplSyntaxOff で囲む。区間内では素の空白はカテゴリコード 9(無視)になり、出力に空きを入れたいときは ~ を使う。
  • \usepackage{expl3} は不要。カーネルに入っており、expl3.sty は読み込みを自分で捨てる。.sty では \ProvidesExplPackage を使えば \ExplSyntaxOn も要らない。
  • 関数は \⟨module⟩_⟨description⟩:⟨signature⟩、変数は \⟨scope⟩_⟨name⟩_⟨type⟩。スコープは l_g_c_
  • nx を取り違えない。 参照を保存したいのか、いまの値を焼き付けたいのかで選ぶ。迷ったら \tl_show:N でログに出して確かめる。
  • 確信のない命令名は使わない。命名は厳密で、公式の interface3 マニュアル(texdoc interface3)が一次資料。