TeX言語は、組版用の言語としては今のところデファクト・スタンダードな地位にありますが、周知の通りお世辞にも「読み書きしやすい言語」ではありません。そのため最近はTeXではない別の言語体系・処理系を提案して、より幸せに組版・開発を行う流れもありますが、一方でかつてより既存のTeX処理系の上で動作する「マトモなプログラミング言語」の開発も行われています。
その代表格として挙げられるのがexpl3ですが、残念ながら今のところ日本語の文献はかなり不足しています。昨年開催されたTeXConf 2017で私は『expl3入門』という講演を行い、その発表資料も公開していますが、スライドだけでは不十分と思われるので、今回は改めてその講演内容に沿った内容をブログ記事の形でまとめてみようと思います。
上記の講演資料と本稿の両者に共通する注意事項として、想定読者は既にTeX言語を習得している方です。いずれは一切の事前知識を要求しないイントロダクションも執筆できたらと思いますが、本稿では一定レベルの「TeX言語の知識」を仮定します。
expl3はLaTeXチームが開発を行っているプログラミング言語です。TeXマクロとして実装されていて、2022年現在日本で使用されているほとんどのTeX処理系で動作します1。expl3は元々“experimental”に由来していましたが、今はやや強引ながら“LaTeX3 Programming Language”の略ということになっており、読み方は「エクスペル・スリー」(x-pel-3)です。また、最近では「L3プログラミング層(L3 Programming Layer)」と表現されることも増えています。
また、本稿の最初の執筆時点では「LaTeX3」というLaTeX2eとはまったく互換性のない新しいバージョンがいずれリリースされる想定がされていましたが、その後のLaTeXチームの方針転換によりLaTeXはLaTeX2eから段階的に進化、新機能の追加が行われるものと位置付けが変化しました2。たとえば現在のLaTeXにはexpl3が最初から組み込まれており、カーネルもexpl3で書かれている部分がどんどん拡大しています。したがって、LaTeX2e・LaTeX3という明確な区別はあまり意味をなさなくなり、単に「LaTeX」と呼ばれるようになっています。合わせて、かつて「LaTeX3チーム」と称していた現在のLaTeX開発チームは最近では「LaTeXチーム」と書かれることが増えています。
主な特徴
expl3はTeX言語がもつ問題点を軽減し、より高い可読性と安全性をもつように設計されています。具体的には、expl3には以下に挙げるようなTeX言語にはない特徴があります。
分類と命名規則
TeX言語では、何らかの「操作を行うモノ」と「データを格納するモノ」がいずれも「マクロ」という形でひとまとめに扱われています。expl3ではこれらはそれぞれ「関数」と「変数」に呼び分けられ、シンタックスの上でも明確に区別されています。
- 関数:引数を取ることができ、展開または実行される
- 変数:値を代入でき、関数の引数として利用される
expl3がプリミティブに提供する関数と変数は、機能や役割に応じて「モジュール」という単位に分類されています。また変数に対しては、格納するデータの種類に応じた「型」が定義されています。関数および変数の命名規則もこうしたモジュールや型に基いて、体系的なものになっています3。
展開制御
展開制御はある意味においてTeX言語の醍醐味の1つですが、一般にはTeX言語を学習したり、他人の書いたTeX言語コードを読み解いたりする上で大きな障壁として認識されてきました。expl3もTeX上に実装されているため「展開制御」の概念自体はなくなりませんが、これをある程度簡単に記述するための機構が導入されています。
充実した公式ドキュメント
TeX on LaTeXの大きな問題点の1つは、LaTeXの内部マクロについて(公式の)十分なドキュメントが用意されていないことです。これに対してexpl3については、LaTeXチームがすべての関数について詳細なドキュメントを作成し、誰でも無料で読めるようになっています(ただし、当然のことながら英語です)。
TeX Liveの利用者であれば、次のようにするとexpl3の公式リファレンスを簡単に開くことができます45。
$ texdoc interface3
expl3のキホン
この節の内容は古くなっています。本稿冒頭のNoteに記した通り、2020年2月のリリース以降LaTeXカーネルにはexpl3がデフォルトで組み込まれているので、expl3を使用するのに\usepackage{expl3}の明示は必須ではありません。もっとも互換性のために(=古いLaTeXでも動作させたい場合)書いても問題はありません。
expl3の文法解説を始める前に、通常のLaTeX文書でexpl3を使用する方法を確認しておきます。これはいたって簡単で\usepackage{expl3}によってexpl3パッケージを読み込むだけで準備が完了します6。expl3パッケージによって\ExplSyntaxOnと\ExplSyntaxOffが定義されるので、これらの間で自由にexpl3コードを書くことができるようになります。
何はともあれ、まずはお決まりのHello World的なプログラムを動かしてみましょう。
\documentclass{article}
\usepackage{expl3}
\begin{document}
\ExplSyntaxOn
\cs_new:Npn \my_hello:n #1 {
Hello~#1!
}
\my_hello:n {expl3}
\ExplSyntaxOff
\end{document}

シンタックス
expl3のシンタックスはTeX on LaTeXとそれほど大きくは違いませんが、2つほど重要な違いがあります。一つは、英文字に加えて**_と:を制御綴に用いることができる**点です。TeX on LaTeXでは(\makeatletterな環境下で)@を制御綴に使用しましたが、expl3では@を制御綴に用いることは基本的にはありません。
もう一つの重要な違いは、半角スペースはすべて無視されるという点です。expl3コード中で「空白」が必要な場合は明示的に~を書きます。この点だけで考えると不便なようですが、この性質のおかげでTeX on LaTeXコーディングでは神経質に気にする必要のあった「行末のコメントアウト」をする必要がなくなります。
変数のカタチ
さて、先述したようにexpl3には関数と変数の区別がありますが、その違いはシンタックス(見た目)の上でも明確になっています。
expl3における変数は、形式的には以下のような形をしています。
\〈スコープ〉_〈モジュール〉_〈説明〉_〈型〉
expl3の変数も関数も実体はTeXの制御綴なので、もちろん最初は\で始まり、そこから〈スコープ〉・〈モジュール〉・〈説明〉・〈型〉が_区切りで連結されます。
まず〈スコープ〉は、その変数にアクセスできる範囲を示す1文字で、具体的には
g: グローバル変数l: ローカル変数c: 定数
の3種のうちのいずれかです。当然のことながら、グローバル変数にはグローバルな代入、ローカル変数にはローカルな代入を行うことになります7。定数は一度定義したら以降値を変更すべきでない変数で、定義されていればどこからでも参照することができます。別の言い方をすると、値を変更されないグローバル変数、ぐらいに理解できます。
続く〈モジュール〉は既に説明したように、変数と関数を役割・機能ごとにまとめる単位で、他の言語でいう名前空間に相当する文字列です。モジュール名は英文字(アルファベット)のみで構成されている必要があり、_などを含めることはできません。基本的なモジュールはexpl3のカーネルで定義されていますが、必要ならパッケージ作成時に自分で作成することもできます8。
変数の3つ目の構成要素〈説明〉は、変数の名称・役割などを表す文字列で、ここには英文字と_を含めることができます。
最後の〈型〉はデータの種類を表す文字列で、expl3標準の型は以下に示す15種類です。
| 型 | 説明 | 型 | 説明 | 型 | 説明 |
|---|---|---|---|---|---|
| bool | 真偽値 | int | 整数 | fp | 浮動小数点数 |
| str | 文字列 | ior | 入力 | iow | 出力 |
| dim | 厳格な寸法 | skip | 伸縮可能な寸法 | muskip | 数式用の寸法 |
| box | ボックス | coffin | 高級なボックス | tl | トークンリスト |
| clist | カンマ区切りリスト | seq | シークエンス | prop | プロパティリスト |
ここで、いくつか具体的な変数の例を挙げてみます。
\l_scfoo_number_of_buttons_int\c_pi_fp
1つ目の例\l_scfoo_number_of_buttons_intは模範的なexpl3の変数で
lが〈スコープ〉scfooが〈モジュール名〉number_of_buttonsが〈説明〉intが〈型〉
です。変数を見るだけで「scfooモジュールに属し『ボタンの数』を表すローカルでint型の変数」ということがわかります。
ところで、expl3カーネルが標準で提供している定数やユーザ用一時変数では、一部モジュール名を含まないものもあります。2つ目に挙げた\c_pi_fpがそれで、$\pi$の値を格納しているfp型の定数です。こうした定数・変数は、特にパッケージなどを作成する場合には自分では定義しない方がよいでしょう。
関数のカタチ
続いてexpl3の関数は、形式的には以下のような形をしています。
\〈モジュール〉_〈説明〉:〈引数指定〉
関数も実体はTeXマクロなので、変数と同様\に始まり_で連結された〈モジュール〉・〈説明〉が続き、最後は:と〈引数指定〉という構成になっています。
関数についても、〈モジュール〉と〈説明〉の部分の制約と役割は変数の場合とまったく同じです。
最後の〈引数指定〉だけは関数に特有で、それぞれの関数がとる引数の形を指定するもので「引数指定子を引数の数だけ並べたもの」です。主な引数指定子には以下のようなものがあります。
n: LaTeXにおける普通の引数{foo}N: 単一トークン(典型的には制御綴)p: パラメタテキストT,F: 真偽に応じて利用される特殊なnw: 例外的なもの
なお、引数を1つもとらない関数の場合は〈引数指定〉は空文字列となります(例:\scan_stop:)。また、展開制御を行う特殊な引数指定子も存在します(後述)。
変数と関数のカタチについて説明したので、少し実際のコードを覗いて見ることにしましょう。先に挙げたHello Worldコードの中では\cs_new:Npnという関数が使用されていました(都合により改行は除いています)。
\cs_new:Npn \my_hello:n #1 { Hello~#1! }
これはTeX言語における\defに相当する関数で、新しい関数を定義する関数です。引数は〈引数指定〉Npnの形からわかるように3つで、上のコード例では
- 第1引数
N:\my_hello:n - 第2引数
p:#1 - 第3引数
n:{ Hello~#1! }
です。これにより\my_hello:n関数が定義されています。
もう一つ別の例を見てみます。
\cs_if_exist:NTF \foo_bar: { true } { false }
関数名から推測できるように、\cs_if_exist:NTFは第1引数に与えた制御綴が存在するかどうかで条件分岐を行う関数です。引数指定子TとFは形の上では普通のnと変わりませんが、引数Tは「条件」が真のとき、引数Fは「条件」が偽のときのみ展開結果に現れます。
すなわち、上記の例では、このコードが実行された時点で\foo_bar:が定義されていればtrueが、そうでなければfalseが紙面に印字されることになります。
簡単なコーディング例
ここまでの内容で、既に単純なexpl3コードを読むのには十分です。ということで、ここで簡単なFizzBuzzコード例を挙げておきます。
% 変数宣言
\int_new:N \g_fzbz_cnt_int
% メインループ(100回)
\int_do_while:nn { \g_fzbz_cnt_int < 100 } {
% インクリメント(グローバル)
\int_gincr:N \g_fzbz_cnt_int
% 剰余の有無を判定
\int_compare:nNnTF { \int_mod:nn { \g_fzbz_cnt_int } { 15 } } = { 0 }
{
FizzBuzz \\
}
{
\int_compare:nNnTF { \int_mod:nn { \g_fzbz_cnt_int } { 3 } } = { 0 }
{
Fizz \\
}
{
\int_compare:nNnTF { \int_mod:nn { \g_fzbz_cnt_int } { 5 } } = { 0 }
{
Buzz \\
}
{
\int_use:N \g_fzbz_cnt_int \\
}
}
}
}
特に難しいところはないので、コード中のコメントを参考に読めると思います。コード(関数名・変数名)から意図が明確に見て取れるのが(TeX on LaTeXに対する)expl3の特徴です。
注意が必要なのは、冒頭の「変数宣言」です。各型(モジュール)には、原則として\〈型〉_new:Nという形の関数が用意されているので、変数は必ず使用前に宣言するようにします。int型の場合は\int_new:Nを用いて変数宣言をしますが、この関数は同時に宣言した変数を0に初期化してくれます。
また、\g_fzbz_cnt_intは形からわかるようにグローバル変数なので、ループ内では\int_gincr:Nによってグローバルにインクリメントしています。int型変数のインクリメントには\int_incr:Nというローカルにインクリメントする関数もありますが、グローバル変数には必ずグローバルな代入・変更を適用するようにしましょう。
そのほか、各関数の詳しい使い方については、公式リファレンスを参照してください。expl3のドキュメントでは、各関数はモジュールごとにまとめられているので、公式リファレンスはexpl3コードを書く際にも大変役立ちます(ほかの多くのプログラミング言語でもそうだと思いますが)。
展開制御と関数定義
特殊な引数指定子
これまでに紹介した、基本的な引数指定子のほかに、expl3には展開制御などに関わる特殊な引数指定子があります。これらの特殊な引数指定子はexpl3の重要な特徴の一つで、これらを用いるとTeX言語では大きな障壁として認識されている「展開制御」をとても簡潔に記述することができます。
特殊な引数指定子を以下に列挙してみます。ここでは、比較のため従来のTeX (on LaTeX)で等価なコードを書くとどうなるかも続けて示しています。
c: 制御綴化
% expl3
\foo_bar:c { baz }
% TeX on LaTeX
\expandafter\foo@bar\csname baz\endcsname
x: 完全展開
% expl3
\foo_bar:x { \baz }
% TeX on LaTeX
\edef\tmp{\noexpand\foo@bar{\baz}}\tmp
o: 1回展開
% expl3
\foo_bar:o { \baz }
% TeX on LaTeX
\expandafter\foo@bar\expandafter{\baz}
f: 先頭完全展開
% expl3
\foo_bar:f { \baz }
% TeX on LaTeX
\expandafter\foo@bar\expandafter{\romannumeral-`0\baz}
V: 変数の値を取り出して使用v: 制御綴化の上、変数の値を取り出して使用(c+V)
具体例から、expl3を使用するとTeX on LaTeXよりはるかにシンプルに展開制御が記述できることがわかると思います。上記の例はすべて引数が1つの場合ですが、引数が複数あるような状況を考えると、その差は歴然です。
関数定義と変種の生成
すでに何度か触れましたが、expl3で関数定義をする最も基本的な方法は\cs_new:Npnを用いることです。この関数のシンタックスはTeX言語の\defとほぼ同一ですが
- デフォルトで
\longが有効であること(定義した関数の引数に\parトークンを含めることができる) - 既に定義済みの関数を定義しようとするとエラーになる
という点はむしろLaTeX2eの\newcommandに近いです。
ところで「関数を定義する関数」には多数の変種があり、例えば
\cs_new:cpn: 動的に制御綴を生成して関数を定義\cs_new_nopar:Npn:\longが無効\cs_set:Npn: 定義済みの関数を上書き(ローカル)
などがあります。
また「関数をコピーする関数」として\cs_new_eq:NNというものも用意されています。これはTeX言語における\letに相当する関数です。
もちろん、この\cs_new_eq:NNにも\cs_new_eq:ccなどの変種が存在していて、関数\my_ナンチャラ_bar:の定義を\my_ナンチャラ_foo:にコピーするような場合に大変便利です。TeX言語でそうした処理をしようと思うと
\expandafter\expandafter\expandafter\global
\expandafter\expandafter\expandafter
\let\expandafter\expandafter
\csname my@#1@foo\endcsname
\csname my@#1@bar\endcsname
などとなり\expandafterがたくさん登場する煩雑で可読性の低いコードになりますが、expl3なら
\cs_new_eq:cc { my_#1_foo: } { my_#1_bar: }
のように簡潔かつ直感的に記述することができます。
実は、この例については次のようにするとTeX言語でも\expandafterの数を減らせます9。
\expandafter\global\expandafter\let
\csname my@#1@foo\expandafter\endcsname
\csname my@#1@bar\endcsname
いずれにしても、expl3版の方がより簡潔で直感的なのではないかなと思います。
ところで、上述したようにexpl3では〈引数指定〉だけが異なる“変種”が多数存在していて、使う側としてはとても便利なのですが、用意する側の視点に立ってみると、いちいち変種を用意する手間が増えそうです。しかし、実はexpl3ではその点もちゃんと考えられていて、\cs_generate_variant:Nnという関数を用いていとも簡単に“変種”を生成することができます。
例えば、\demo_cmd:Nnnという3引数をとる関数が定義されているとき
\cs_generate_variant:Nn \demo_cmd:Nnn { cnx }
を記述するだけで\demo_cmd:Nnnと同様の機能をもつ変種\demo_cmd:cnxが自動的に用意されます。
コーディング作法
さて、以上で既にexpl3文法のあらましを説明し終えたので、ここで実際にexpl3コードを書く上での実践的な注意について述べておきます。これらはexpl3における「コーディング作法」とでも呼ぶべきもので、コードの安全性・可読性・保守性を維持するためにも、ぜひとも守って欲しいルールです。
1. TeXプリミティブを使わない
expl3はTeX言語ではないので、当然TeXプリミティブをそのままの形で使うべきではありません。
一方で、機能・実体はTeXプリミティブであっても、expl3カーネルによって関数化されているもの(例えば\scan_stop:はプリミティブ\relaxの別名に過ぎません)は使用しても構いません10。
2. 各モジュールの内部変数や内部関数は使わない
expl3では、各モジュールが内部処理に用いるプライベートな要素には〈モジュール〉の前に__を前置することになっています。
- プライベート変数の例:
\l__foo_bar_tl - プライベート関数の例:
\__foo_bar:Nn
言うまでもなく、これらの内部関数をモジュール外で使用するべきではありません。
3. 多少冗長であっても、可読性を重視する
これはexpl3に限ったことではありませんが、なるべく可読性の高いコードを書くことは保守性の観点からも重要です。具体的には
- 変数名や関数名を十分説明的にすること
- 積極的にスペーシングや改行を行うこと
を心がけるといいでしょう。
4. 内部関数はなるべく\long有効にする
先ほども説明したように\cs_new:Npnはデフォルトで\long有効です。特別な理由がある場合を除き、むやみに\cs_new_nopar:Npnを使うべきではありません。
5. 変数は必ず宣言してから使用する
すべての変数は\〈型〉_new:Nのような関数を用いて先に宣言をしてから使用するようにしましょう。実装上の関係で、いくつかの型の変数については宣言を省略しても使えてしまいますが、expl3ではこのような使い方はサポートされていません。
便利なモジュール
expl3には文法的なメリット(可読性や展開制御の書きやすさ)のほかに、l3kernelによって提供されるいくつかの非常に便利なモジュール(ライブラリ)を利用できることが挙げられます。そうしたモジュールひとつひとつの詳しい説明は公式リファレンスや他の解説記事に譲りますが、いくつか筆者が便利だと思う具体的なモジュールを挙げておきます。
l3keys
LaTeX2eでもgraphicxパッケージなどを筆頭としてkey=valueの形でオプションを与える命令を目にしたことがあるかと思います。TeX on LaTeXでこうした機能を実装するためには、専用のパッケージを読み込む必要がありましたが、expl3では標準モジュールとしてkeyval形式をサポートするライブラリが組み込まれています。このライブラリはかなり柔軟性が高く、またkeyvalの設定自体をkeyval形式のシンタックスで記述できるので可読性も高く便利です。
l3regexp
expl3には正規表現をサポートするl3regexpモジュールがあり、これがかなり強力です。LaTeX上のプログラミングでは文字列の検索・置換といった操作をしたい場面は当然多いので、このモジュールの存在はとても心強いです。
LaTeXパッケージの作成
expl3でLaTeXパッケージを作成する際には、ファイル冒頭で\ProvidesExplPackageを用いてパッケージ宣言をすると便利です。
\ProvidesExplPackage{〈名前〉}
これにより、そのパッケージファイル(*.sty)では自動的にexpl3シンタックスが有効になるので、\ExplSyntaxOnの明示が不要になります。
開発支援ツール
expl3の文法はいわゆるTeX言語と大差がないので、TeX用の開発支援ツールをそのまま使用してもある程度の利便性は得られると思います。しかし、expl3では関数名・変数名に_や:が入るので、シンタックスハイライトぐらいは専用のものがあると便利です。
expl3専用の開発支援ツールはまだそれほど多くはありませんが、以下のようなものがあります。
- TeXworks: LaTeXチームのJoseph Wright氏によるシンタックスハイライトあり
- WinEdit: 標準でサポート
- Emacs: AUCTeXがexpl3に対応している
- Vim: 拙作vim-expl3がシンタックスハイライトを含む基礎的なサポート機能を提供
おわりに
expl3を使うことの最大の利点は、その特徴的な命名規則による意図の明確化と名前空間の分離、そして展開制御を簡潔に記述するための仕組みによって、TeX言語よりも一段と可読性・保守性を高めることができる点です。加えて開発したいパッケージの種類によっては、l3kernelの提供する強力なライブラリ機能が利用できることも強みです。
上記の利点が活かせそうなLaTeXパッケージ(特に、ある程度大規模または複雑な処理を行う必要が想定されるもの)を開発したいと思い至ったときは、ぜひexpl3の採用を考えてみてください。
参考文献
expl3の第三の特徴として挙げたように、expl3には非常に充実した公式のリファレンスがあります。なお、本稿の参考文献もすべてLaTeXチームによる公式ドキュメントです。
- Theexpl3package and LaTeX3 programming (expl3.pdf)
- The l3build Package (l3build.pdf)
- The LaTeX3 Interfaces (interface3.pdf)
- The LaTeX3 kernel: style guide for code authors (l3styleguide.pdf)
主な更新履歴
- 2021-04-04:
\longの説明が誤っていたので修正 - 2025-11-25: 2025年現在の状況に合わせた全体的な内容・スタイルの更新
-
正確には「TeX90の全プリミティブとe-拡張のほとんどのプリミティブ、およびpdfTeXの
\pdfstrcmp相当の機能」を有するTeX処理系である必要があります。v1.40以降のpdfTeX、v0.9994以降のXeTeX、v0.70以降のLuaTeXそしてmid-2012以降のe-(u)pTeXはこれに該当します。 ↩︎ -
詳しくはLaTeX News 40(または当ブログの斜め読み)を参照。 ↩︎
-
普通の言語ではこの程度は「当たり前のコト」かもしれませんが、TeX言語ではまったくこのようになっていませんでした。例えば
\romannumeralは本来は(文字通り)数値をローマ数字の「文字列化」するための命令ですが、TeX言語者によってしばしば本来の目的とはまったく異なる邪悪な使い方をされていました。 ↩︎ -
texdoc expl3で開くことのできるThe expl3 package and LaTeX3 programming (expl3.pdf)は、リファレンスというよりはexpl3の簡単な紹介をする文書になっています。 ↩︎ -
一方
texdoc source3とするとThe LaTeX3 Sources (source3.pdf)という、interface3.pdfの内容に加えて内部実装そのものを含む文書を開くことができます。これらの文書名・ファイル名はLaTeX2eの実装を紹介する公式ドキュメントThe LaTeX2e Sources (source2e.pdf)に由来しているようです。 ↩︎ -
内部的にexpl3パッケージを読み込むxparseパッケージを
\usepackageしてももちろんOKです。ところで、若干紛らわしい感じもしますが「expl3パッケージ」はLaTeX2e用のパッケージです。 ↩︎ -
ローカル変数にグローバルな代入をしないのは当たり前ですが、グローバル変数にローカルな代入をすることも推奨されません。 ↩︎
-
既存のモジュール名はLaTeXチームの管理するl3prefixes.csvというファイルにリストアップされています。新たにモジュール名を作成するときは、このリストにあるモジュール名と衝突しないように注意しましょう。 ↩︎
-
読者の方に教えていただきました。情報ありがとうございます。 ↩︎
-
l3namesではLaTeXチームがカーネルを記述するためにすべてのTeXプリミティブを
\tex_〈プリミティブ〉:Dというexpl3式の命令にコピーしていますが、これらは一般のexpl3ユーザは使用すべきでありません。そもそも、ここで登場している〈引数指定〉Dは“Do NOT use”の意味です。 ↩︎