xparse(\NewDocumentCommand)

LaTeX チームが汎用の引数パーサ xparse に着手したのは 1990 年代の終わりで、その成果である \NewDocumentCommand が実験的なパッケージを卒業して LaTeX カーネルに入ったのは 2020 年 10 月 1 日のリリースでした。二十年余りを要したのには理由があります。\newcommand は「引数がいくつか」しか数えられないのに対し、\NewDocumentCommand は「どんな引数か」を 引数指定(argument specification, arg-spec) という文字の並びで宣言します。数えることと記述することの差は、星付きの変種、複数のオプション引数、区切り文字つきの引数といった、\newcommand では一行も書けない構文をまるごと手に入れることでした。このページでは指定子の一文字ずつが何を約束するのか、\IfNoValueTF\IfBooleanTF をどう使い分けるのか、そして逆にこれを持ち出さないほうがいい場面を扱います。

\newcommand では書けない形 —— オプション引数は先頭にひとつだけ

\newcommand が作れる形はひとつだけです —— 角括弧のオプション引数を 最大ひとつ、しかも先頭に限って 置き、そのあとに必須引数を並べる。この一形態しかありません。それ以上の構文が欲しければ TeX の \def プリミティブと低水準のマクロ programming に降りるしかない、と LaTeX News 32 は当時の状況を記しています。だから歴史的なパッケージのソースには、次に来るトークンを覗き見る仕掛けが手書きで並んでいます。星の有無を見る \@ifstar、任意の文字を見る \@ifnextchar —— 名前に @ を含むので \makeatletter で囲む必要があり、空白の扱いと入れ子で簡単に壊れ、そして読めません。

\NewDocumentCommand はこの覗き見の作業を 宣言的な文法 に置き換えます。数字ではなく文字の並びを渡すと、パーサが入力を読み取り、本体には常に正規化された #1#2 … として届けてくれる。利用者から見えるインタフェースと、実装のコードが切り離されるわけです。この仕組みはカーネル内では ltcmd というモジュール名で実装されており、2020 年 10 月 1 日のリリース以降 \usepackage{xparse} は要りません。xparse パッケージ自体はいまも CTAN にありますが、それを収める l3packages バンドルの README は自らを「Deprecated」と題し、古いファイルを動かし続けるために残してあると述べています。例外は g/Glu という非推奨の引数型で、これらを使うと Invalid argument type "g" in command "\zzz" (requires xparse). というエラーになります。新しいコードでこれらに手を伸ばす理由はまずありません。

\NewDocumentCommand の書き方と New / Renew / Provide / Declare の違い

基本の形は \NewDocumentCommand{\cmd}{⟨arg-spec⟩}{⟨本体⟩} の三引数です。第 1 に作る命令名、第 2 に引数指定、第 3 に本体を書き、本体では #1#2 … で引数を受け取ります。頭の動詞を差し替えると、既存の名前に出会ったときの態度が変わります。\NewDocumentCommand で既に定義済みの名前を狙うと LaTeX cmd Error: Command "\section" already defined. で止まる —— これは事故防止であって嫌がらせではありません。同じ名前を上書きしたいなら \RenewDocumentCommand、まだ無いときだけ定義したいなら \ProvideDocumentCommand を使います。

定義命令すでに定義済みの名前に対する振る舞い
\NewDocumentCommand定義済みならエラーで停止する。既定の選択肢
\RenewDocumentCommand未定義ならエラー。既存の命令を作り直すときに使う
\ProvideDocumentCommandまだ無いときだけ定義する。パッケージが互換の穴を埋める用途
\DeclareDocumentCommand問答無用で上書きする。公式ドキュメントも「控えめに」と釘を刺している

この四つで作った命令には、頼まなくても付いてくる性質があります。最初から頑強(robust) なのです。定義した命令に \meaning を当てると \protected macro:->… と表示され、ε-TeX の \protected 機構が使われていることが確かめられます。見出しや図表のキャプションのような「可動引数」に置いても、\protect を前置する必要はありません。\newcommand で作った命令がなぜそこで壊れるのか、\DeclareRobustCommand が何をしていたのかは「マクロの定義」のページに譲ります。

引数指定子の一覧 —— m o O{} s t r d e v b が表すもの

引数指定は 1 文字が 1 引数 の文字列で、指定子は必須の族とオプションの族に分かれます。必須の族は mrRvb、オプションの族は oOdDsteE です。ここに一貫した規則がひとつあります —— 大文字は既定値を自分で決められ、小文字は代わりに -NoValue- という特別な印を返すoO{...}dDeErR の対はすべてこの規則で読み解けます。内部的には odO はいずれも適切に構成した D 型への近道にすぎない、と公式ドキュメントは明かしています。

指定子意味本体での受け取り
m必須引数。{...} でも単一トークンでもよい外側の波括弧を外した通常の #1
rr⟨d1⟩⟨d2⟩ —— 自分で選んだ区切り文字で囲む必須引数開き側が無ければエラーの後 -NoValue-
RR⟨d1⟩⟨d2⟩{既定値} —— r と同じだが復旧値を自分で決める無ければ書いた既定値
v\verb と同じ要領のベルバティム引数。区切り文字に % \ # { } と空白は使えない文字をそのまま。他の命令の引数の中では使えない
b環境の本体。\NewDocumentEnvironment 専用で、指定の末尾に置く\begin\end に挟まれた中身
o標準的な [...] のオプション引数与えられなければ -NoValue-
OO{既定値} —— o に既定値を付けたもの与えられなければ既定値。常に値が入る
dd⟨d1⟩⟨d2⟩ —— 任意の区切り文字によるオプション引数与えられなければ -NoValue-
DD⟨d1⟩⟨d2⟩{既定値} —— d に既定値を付けたもの与えられなければ既定値
s先頭の星 * の有無を見る\BooleanTrue\BooleanFalse
tt⟨char⟩ —— 指定した 1 文字の有無を見る(s の一般化)\BooleanTrue\BooleanFalse
ee{⟨tokens⟩} —— ^_ のような装飾子の集合。トークンは互いに異なることトークンごとに 1 引数、無いものは -NoValue-
EE{⟨tokens⟩}{⟨既定値の並び⟩} —— e に既定値を与えたもの既定値の並びが短ければ、余った分は -NoValue-

区切り引数(rRdD)には守るべき制約があります。まず、TeX のグループ化に使う {} は区切り文字にできません。r{} と書くと LaTeX cmd Error: Argument delimiter "" invalid in command "\zzz". で拒まれます。[]()<>"" のように自然に対になる文字を選ぶのが定石です。もうひとつ、区切りが 文字トークン の場合、パーサは定義した時点のカテゴリコードを覚え込みます。あとから < を文字扱いに変えたりすると、同じ < がもう区切りとして認識されません。逆に区切りが制御綴(\x のようなもの)なら、意味が変わっても名前で識別されるので影響を受けません。

latex
% t<char> tests for one character; r()...() is a required delimited argument
\NewDocumentCommand{\pt}{t+ r()}{%
  \IfBooleanTF{#1}{\mathbf{(#2)}}{(#2)}%
}
$\pt(1,2)$    % -> (1,2)
$\pt+(3,4)$   % -> (3,4) in bold

% e{^} picks up an optional ^ embellishment wherever it appears
\NewDocumentCommand{\deriv}{e{^} m m}{%
  \frac{\mathrm{d}\IfNoValueF{#1}{^{#1}}#3}{\mathrm{d}#2\IfNoValueF{#1}{^{#1}}}%
}
$\deriv{x}{f}$        % -> df/dx
$\deriv^{2}{x}{f}$    % -> d^2 f / dx^2

+ ! > = —— 指定子の前に付ける修飾子

+ はその引数を 長い(long)引数 にし、空行すなわち段落の区切りを飲み込めるようにします。ここに \newcommand から乗り換えた人がまず踏む地雷があります —— 既定が逆向きなのです。 \newcommand は引数をすべて長くし、短くしたいときに星付きの \newcommand* を使う。\NewDocumentCommand はその逆で、引数は既定で短く、長くしたい引数の前にだけ + を書きます。だから移植したての命令に段落をまたぐ本文を渡すと ! Paragraph ended before \remark was complete. が出ます。引数ごとに指定できるのは、たとえば「短い見出しは 1 段落まで、本文は複数段落可」を型で表現できるということでもあります。

残る三つも短く押さえておきます。! は「オプション引数の直前に空白を許さない」という指定で、末尾のオプション引数にしか付けられません(先頭に付けると Invalid argument prefix "!" in command "\remark". になります)。\foo{x} [x] の角括弧を本文として扱いたい場面で効きます。>引数プロセッサ を挟む記号で、>{\SplitArgument{2}{;}} m と書けば a;b;c を三つの引数に割ってから本体に渡せます。カーネルには \SplitArgument\SplitList\TrimSpaces\ProcessList\ReverseBoolean が用意されています。= はオプション引数を キー=値 として解釈させる比較的新しい修飾子で、\caption のように従来「自由なテキスト」を受け取ってきた命令にキー=値の入口を後付けするために導入されました。

latex
% + makes ONE argument long; ! on a trailing optional argument forbids a space
\NewDocumentCommand{\remark}{+m !o}{\par\textbf{Note.} #1 (#2)\par}

\remark{first paragraph

  second paragraph}[tag]
\remark{x} [these brackets stay ordinary text]

% > runs a processor before the body sees the argument
\NewDocumentCommand{\triple}{>{\SplitArgument{2}{;}} m}{\tripleaux#1}
\NewDocumentCommand{\tripleaux}{m m m}{(#1/#2/#3)}
\triple{a;b;c}   % -> (a/b/c)

\IfNoValueTF\IfBooleanTF の使い分け、そして oO{} の違い

判定命令は二系統あり、対応する指定子で選びます。ode のように -NoValue- を返す型には \IfNoValueTF{#1}{⟨無かった場合⟩}{⟨有った場合⟩}st のように真偽値を返す型には \IfBooleanTF{#1}{⟨真⟩}{⟨偽⟩} を使います。論理を反転した \IfValueTF もあり、どちらの系統にも片枝だけの \IfNoValueT\IfNoValueF\IfValueT\IfValueF\IfBooleanT\IfBooleanF が揃っています。ここで \IfNoValueTF が存在する理由そのものが面白い —— 省略されたオプション引数と、空で与えられたオプション引数は別物 だからです。\newcommand の既定値の仕組みは両者を区別できません。未指定なら既定値が入るだけで、「そもそも書かれなかった」という事実は本体に届かないのです。

この -NoValue- はよくできた偽物避けです。文字列としての -NoValue- とは一致しないように作られている ので、\IfNoValueTF{-NoValue-} は論理的に偽になります。つまり文字列比較で代用しようとしても通りません —— 必ず \IfNoValueTF で判定してください。そして落とし穴は oO{} の取り違えです。o なら未指定時に -NoValue- が入るので \IfNoValueTF が効きますが、O{}常に値が入る(未指定なら空文字列)ので \IfNoValueTF はいつも偽の側に落ちます。判定を忘れたまま組版すると、PDF に文字どおり -NoValue- と印刷されて初めて気づく、という事故もよく起きます。

では O{} の中身が空かどうかを見たいときは何を使うか。ここは公式ドキュメントの推奨が 2022 年 6 月に変わった箇所です。カーネルは \IfBlankTF\IfBlankT\IfBlankF も)を提供しており、引数が本当に空か、空白だけかを判定します。オプション引数がふたつ並ぶような設計では、-NoValue- と「空」を別々に調べるより、O{}\IfBlankTF の組み合わせ のほうが素直だ、と公式ドキュメントは述べています。expl3 の \tl_if_blank:nTF や etoolbox の \ifblank をわざわざ持ち出す必要はありません。なお \IfBlankTF\space のような命令を「中身あり」と数えます —— 出力は空白でも、トークンとしては実体があるからです。

latex
% s = optional star, o = optional [..], m = mandatory
\NewDocumentCommand{\heading}{s o m}{%
  \IfBooleanTF{#1}
    {\section*{#3}}%                      starred: unnumbered
    {\IfNoValueTF{#2}
       {\section{#3}}%                     no short title given
       {\section[#2]{#3}}}%                short title for the ToC
}

\heading{A Long Introduction}            % numbered section
\heading[Intro]{A Long Introduction}     % short title in the table of contents
\heading*{Preface}                       % unnumbered

% with O{} the value is always there, so test for blankness instead
\NewDocumentCommand{\tagged}{O{} m}{\IfBlankTF{#1}{#2}{[#1] #2}}

この見出しの例には、\newcommand では真似のできない性質がもうひとつ隠れています。\NewDocumentCommand で作ったオプション引数は安全に入れ子にできる のです。公式ドキュメントの例で言えば、\foo[\baz[stuff]]{more stuff} のようにオプション引数の中へオプション引数つきの命令を書いても正しく解析されます。\newcommand の角括弧は素朴に「次の ] まで」を取るので、同じことをすると中の ] で切れてしまう。オプション引数を受ける命令を別のオプション引数の中に置きたくなったら、その時点で \NewDocumentCommand に移る理由が十分にあります。

\NewDocumentEnvironmentb 型 —— 環境の中身を引数として受け取る

環境にもそっくり同じ仕組みがあり、\NewDocumentEnvironment{⟨env⟩}{⟨arg-spec⟩}{⟨開始コード⟩}{⟨終了コード⟩} で定義します(\Renew…\Provide…\Declare… も同様)。引数は \begin{⟨env⟩} の直後に与えられ、開始コードからも終了コードからも参照できます。ここに命令側には無い指定子が加わります —— b、環境の本体そのもの です。引数指定の末尾に b を置くと、\begin\end に挟まれた中身が丸ごとひとつの引数として届き、加工したり、二度組んだり、条件で捨てたりできます。

b を使うときの作法が三つあります。第一に、本体は既定で 両端の空白が除かれる ので、行末の空白を気にせず書けます。逆に空白を残したければ !b と書きます。第二に、複数段落を許すなら +b にします。第三に —— これは忘れやすい —— b を使うと終了コードは実質不要になりますが、空の第 4 引数は省略できません。書き忘れると \NewDocumentEnvironment が引数を数え損ねます。なお b を使った環境どうしは入れ子にできます。\newenvironment の基本と、b を使わない素直な環境の作り方は「環境の自作」のページにあります。

latex
% b grabs the whole body; + allows several paragraphs; the empty 4th
% argument is still required even though there is no end code left to run
\NewDocumentEnvironment{shout}{O{\bfseries} +b}{#1#2}{}

\begin{shout}[\itshape]
  Loud and clear.
\end{shout}

\NewExpandableDocumentCommand が要る場面 —— 表のセルの先頭と \edef の中

通常版が頑強であること —— つまり簡単には展開されないこと —— は、ほとんどの場面で利点ですが、たまに邪魔になります。もっとも現実的な例が 表のセルの先頭 です。標準の tabular の仕組みは、\multicolumn などを包む命令が展開可能であることを要求します。\NewDocumentCommand で作った命令はエンジンの機能でわざと展開を止めているので、ここでは使えません。\edef\write の中で中身を確定させたい場合も同様です。そのために \NewExpandableDocumentCommand(および \Renew…\Provide…\Declare…)があります。ただし公式ドキュメントは「本当に必要なときだけ」と明言しています。制約が付くからです。

  • 引数があるなら、最後の引数は必ず mrR のいずれか(必須の型)でなければなりません。
  • ベルバティム型 v は使えません> の引数プロセッサと = のキー=値も同様に使えません。
  • \foo[\foo{[} を区別できません。どちらの [ もオプション引数の開始と解釈されるため、オプション引数の検出は通常版より脆弱です。
  • 一方で st の真偽引数は問題なく使えます\IfBooleanTF も展開可能なので、\edef の中でも期待どおりに分岐します。
latex
% a command wrapping \multicolumn must be expandable to work in a cell
\NewExpandableDocumentCommand{\wide}{m}{\multicolumn{3}{c}{#1}}

\begin{tabular}{lcr}
  a & b & c \\
  \wide{spans three columns} \\
\end{tabular}

\newcommand\NewDocumentCommand はどちらを使うべきか

引数が無いか、必須引数がひとつふたつあるだけの短縮マクロなら \newcommand で十分です。 \newcommand{\R}{\mathbb{R}}\NewDocumentCommand で書き直しても、長くなるだけで何も得られません。\newcommand は古びてもいなければ非推奨でもなく、\NewDocumentCommand の登場後も LaTeX の正規の道具です。乗り換えるべき合図はむしろはっきりしています —— 星付きの変種が欲しくなったとき、オプション引数がふたつ必要になったとき、[...] 以外の入力構文を受け取りたくなったとき、そしてオプション引数の中に別のオプション引数つき命令を置きたくなったとき。どれかひとつでも当てはまれば、\@ifstar を手書きするより引数指定を一行書くほうが確実に短く、確実に読めます。

最後にもうひとつ、逆向きの目安を。\NewDocumentCommand入力構文を設計する道具 であって、本体の処理を書く言語ではありません。引数を受け取ったあとで文字列を分解したり、条件分岐を重ねたり、繰り返したりしたくなったら、それは expl3(LaTeX3 プログラミング層)の領分です。実際、ltcmd 自身が expl3 で書かれています。逆に、パッケージやクラスの中で利用者向けの命令を設計するときは \NewDocumentCommand が第一候補になります —— 引数指定がそのままインタフェースの仕様書として読めるからです。