\usepackage{expl3} と書いても、いまの LaTeX では何も読み込まれません。 expl3 —— かつて「LaTeX3」と呼ばれた取り組みから生まれた LaTeX のプログラミング層 —— は、すでにフォーマットの一部だからです。実際、カーネルのソース latex.ltx の 1125 行目には Skipping: expl3 code already part of the format というログ出力があり、expl3.sty は 53 行目で「もう入っているか」を確かめて、入っていればコードの読み込みを丸ごと捨てます。このページでは、\ExplSyntaxOn が文字の扱いをどう変えるのかを実測し、\module_function:nn という独特の名前の読み方、引数指定子、そして tl・seq・prop・int・fp といったデータ型を順に見ていきます。
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 の中では、~ は「空白を出すコマンド」ではなく文字どおり空白そのものなのです。
| 文字 | 通常の LaTeX | ExplSyntaxOn の区間 |
|---|---|---|
(space) | 10 — 空白 | 9 — 無視される文字。 インデントも改行も出力に影響しない |
~ | 13 — アクティブ(非改行空白) | 10 — 空白そのもの。 出力に空きを入れたいときはこれを書く |
_ | 8 — 下付き添字 | 11 — 文字。 命令名の中に書ける |
: | 12 — その他の文字 | 11 — 文字。 引数シグネチャの区切りに使える |
この交代の実務上の意味は一つです。expl3 の中で素の空白を書いても出力には出ません。 「Fruit: apple」と組みたければ Fruit:~#1 と書く必要があり、~ を素の空白に替えると「Fruit:apple」とくっついてしまいます。裏を返せば、コードを好きなだけインデントし、トークンのあいだに空きを入れて読みやすく書いても、出力は一切変わらないということです。ふつうの LaTeX が空白に神経を使う言語であることを思えば、これはかなりの解放です。
% 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 | 先頭から左→右に、最初の展開不可トークンまで展開。 |
p | TeX の パラメータテキスト(#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_tl を SECOND に変えて両方を出力すると、n 版は「[SECOND]」、x 版は「[FIRST]」。つまり n は参照を、x はスナップショットを保存するわけです。展開の制御が難しいと言われる場面のほとんどは、この違いの選び間違いです。\tl_show:N は詰まったときの最初の一手として覚えておく価値があります。
\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 | 浮動小数点。sin・sqrt・pi など科学関数も使える | \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 で分岐する |
\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 の引数指定(m・O{...}・s など「文書レベル」の引数)と、expl3 の引数シグネチャ(N・n など「プログラミングレベル」の処理指定)は別のものなので混同しないこと。前者は 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_。 nとxを取り違えない。 参照を保存したいのか、いまの値を焼き付けたいのかで選ぶ。迷ったら\tl_show:Nでログに出して確かめる。- 確信のない命令名は使わない。命名は厳密で、公式の interface3 マニュアル(
texdoc interface3)が一次資料。