ト部蛸焼のブログ

日頃知ったことをアウトプットするためのブログ

\NewDocumentCommandによる自由な引数設定

こんにちは,ト部蛸焼です.

今回は,LaTeXに関する記事です.
具体的には,LaTeXのコマンドを定義する際にxparseパッケージを用いることで,従来の「\newcommand」よりも柔軟な定義ができることをまとめます.また,これを実際のコマンド定義に用いた例として,同一コマンド異引数の条件で確率と条件付き確率を出力し分けるコマンドを定義してみます.

xparseパッケージ

\newcommandによる定義

\newcommandによる定義の厄介さは[1]に詳しく記述されています.今回は特にオプション引数*1に注目します.

newcommandによるコマンドの定義において,オプション引数は1番目の引数のみにその権利が与えられます.つまり,2番目以降の引数をオプション引数に設定することはできません.

\newcommand{hoge}[3][]{#1の#2#3}
\hoge[]{}{長い}  % 出力は「象の鼻は長い」
\hoge{}{}      % 出力は「のどは喉」

% これはエラーとなる例
\newcommand{hoge}[3][][]{#1の#2#3}
% 2つ目の引数を選択的にしようと試みるも……

以降でこの不都合さが直接効いてくる部分はありませんが,選択的にできる引数が1番目のみと限られると,作ったコマンドや環境が使いにくいこともあります*2


\NewDocumentCommandによる定義

ここではxparseパッケージ [2] *3で定義されている\NewDocumentCommandというコマンドについて簡単に説明します*4

\NewDocumentCommandの文法は以下の通りです[4]

\NewDocumentCommand ⟨コマンド名⟩ {⟨引数の種類⟩} {⟨定義内容⟩}

ここで,「引数の種類」には「m」「o」「O」などを指定します*5.それぞれの用法は以下のとおりです.

指定子 用法
m 通常の必須な引数
o 通常のオプション引数
O{<default>} 基本は「o」で,デフォルト値を<default>に指定する

さて,\NewDocumentCommandはどんな利点をもたらすのでしょうか.そのうちの一つはオプション引数の自由度にあります.例を見ていきましょう*6

% \conjugateの定義
\NewDocumentCommand{conjugate}{ m O{#1ed} O{#2} } {(#1, #2, #3)}
% 活用させる
\conjugate{walk}            % 出力: (walk, walked, walked)
\conjugate{find}[found]     % 出力: (find, found, found)
\conjugate{do}[did][done]   % 出力: (do, did, done)

ここで注目すべきは2つです*7

  • 2つ目以降の引数にオプション引数を割り当てられている点
  • 複数個のオプション引数を使用できている点

これで,\newcommandで生じていた不都合を改善できそうです.


実践

前章で説明した\NewDocumentCommandを用いたコマンドを実際に作成します*8

英訳の有無に合わせて出力様式を変えるコマンドを作る ~\newwords~

このコマンドの要件は以下の通りです.

設定 ・ 新出の用語をハイライトする.
・ 対応する英訳を付記することもあるが,これは必須ではない.
入力 用語だけ,または日本語の用語とその英語訳
出力 用語だけの場合は「 用語 」,英訳付きの場合は「 用語 (English) 」

この要件を満たすコマンド\newwordsを以下のように作成することができます*9

% 定義
\NewDocumentCommand{\newwords}{mo}{ \textbf{#1}\IfValueT{#2}{ (\textbf{#2})} }
% 使用例
あれを\newwords{Euclid整域}という.
あれを\newwords{代数閉包}[algebraic closure]という.

出力例を以下の図に示します.

f:id:tobetakoyaki:20200426173047p:plain:w500
\newwordsの使用例に対する出力例


確率と条件付き確率のコマンドを作る ~\prob~

おまけです.このコマンドを以下の設定の下で作成します.

設定 ・ 確率の記号 \Pr[ \; ]を出力する.
・ 条件付き確率も同じコマンドを利用して出力したい.
 \Prは「\Pr」という数学演算子として既に定義されている.

追加の設定として,条件付き確率を表示させる際,間に入る縦棒を上下に伸縮させられるようにしておきます*10.この部分については[5]を参考にしました.

すると,以下のようなコマンド\probを作成できます*11

% 定義
\NewDocumentCommand{\prob}{mo}{%
  \Pr \left[ #1 %
  \IfValueT{#2}{%
    \mathrel{} \middle| \begin{array}{l} #2 \end{array}%
  }%
  \right]%
}
% 使用例
\prob{X=a}
\prob{f(z) = y}[%
  x \overset{\mathsf{U}}{\leftarrow} \{0,1\}^{k}\\%
  y = f(x)\\%
  z \overset{\mathsf{R}}{\leftarrow} \mathcal{A}(1^{k}, y)%
]

出力例を以下の図に示します.求められていた出力ができました.

f:id:tobetakoyaki:20200426180232p:plain
\probの使用例に対する出力例


参考文献

1. xparse パッケージでスゴイ LaTeX マクロを作ろう! - Qiita
2. CTAN: Package xparse
3. The LaTeX3 Project
4. The xparse package Document command parser (Released 2020-03-06)
5. \middle であり \mid であるもの - マクロツイーター



追記 (2020.05.03)

コマンド\probについて追記です.
本編ではarray環境を使って条件部分を作成しましたが,これをgathered環境を使って作成する方法を与えます.

まず,本編にある実装方法では条件が1行で書かれている場合に少し見栄えが悪くなることを指摘します.

f:id:tobetakoyaki:20200503175215p:plain
1行条件の場合における\probの出力

yと縦棒の距離と縦棒とxの距離が違い,条件部分の位置が少し下に下がっています.見栄えが悪いです.

これをgathered環境を使って改善しましょう.gathered環境は複数の数式を中央寄せでまとめます.今回は左寄せで出力することを理想としておりますので,その部分を調整する必要があります*12.これは「\hfill」で解決します

\NewDocumentCommand{\probnew}{mo}{%
  \NewDocumentCommand{\br}{}{\hfill\\}% 改行を細工
  \Pr \left[ #1 %
  \IfValueT{#2}{%
    \mathrel{} \middle| \begin{gathered} #2 \end{gathered}%
  }%
  \right]%
}

それではここで新しく定義したものと過去のものを比較しましょう.

\begin{align*}
  &\prob{f(z) = y}[%
    x \overset{\mathsf{U}}{\leftarrow} \{0,1\}^{k}%
  ]\\%
  &\probnew{f(z) = y}[%
    x \overset{\mathsf{U}}{\leftarrow} \{0,1\}^{k} \br%
  ]%
\end{align*}

出力は以下のようになります.

f:id:tobetakoyaki:20200503180511p:plain
新旧の比較 (上が旧,下が新)

見栄えがマシになりました.以上です.


追記 (2020.07.24)

2020.05.23追記分に修正を加えました.具体的には,「The new definition of \prob(new)」と題したプログラムの中で,gatheredという環境を用いていますが,そこに入っていたオプション引数[t]を削除しました.
これがあると例えば条件式が2行*13のときに,3段表示になってしまい,その2段目と3段目に文字列が出力されてしまいます.訂正してお詫びいたします.

なお,gatheredではなくてalignedを用いることも可能です.この場合も[t]を付けた処理は同じ効果をもたらします.
また,alignedを用いると,「&」を使って式の位置を揃えたくなりますが,今回のようにそれをオプション引数の中で利用するためには

$\prob{f(z) = y}[{%
  &x \overset{\mathsf{U}}{\leftarrow} \{0,1\}^{k}\\%
  &y = f(x)\\%
  &z \overset{\mathsf{R}}{\leftarrow} \mathcal{A}(1^{k}, y)%
}]%

のように{ }で全体を括る必要があります.(参考:
Cannot use ampersand as argument in user defined command with optional arguments - TeX - LaTeX Stack Exchange)

なお,このとき「条件部分の位置が少し下に下がる」という現象は避けられませんが,これは例で用いた文字列が高さが上に長いからである可能性があります.現に,  \overset{\mathsf{U}}{\leftarrow} \inに変更すると位置は揃います.

このような場合にも完全に位置を揃えるためには例えばarray環境と\multirowコマンドを駆使して列方向に連結した表を作ることが得策かもしれませんが,それはまた別の機会に述べるかもしれません.

*1:オプション引数とは,コマンド (command) や環境 (environment) を用いる際に入力が選択的 (optional) な引数を指します.必須 (mandatory) な引数は「{ }」で,選択的な引数は「[ ]」でそれぞれ囲んで入力し分けます.例えば,amsthmパッケージから,よくある方法で「theorem」という環境を作った場合,「\begin{theorem}[いい定理]」などと記述できますが,このときの「[いい定理]」の部分がオプション引数に当たります.

*2:例えば選択的にしたい部分が出力として後方にあるとしましょう.この場合,前方から入力していくその初めに「後方で出てくる予定の文字を先に入力しておく」ことになります.これでは「こう出力させたい」と浮かぶ順番と「こう入力しなければならない」の順番が逆転してしまい,ちょっと頭を使う必要が生じます.LaTeX打ちは単純作業でないといけない [要出典] ので,これではいけません.

*3:このパッケージはLaTeX3プロジェクトの成果の一部です.このプロジェクト自体は1990年前半に端を発しており,従来のLaTeXにおける様々な制約を緩和し,より自由に文書の作成ができるよう計画が進められています[3]

*4:\newcommandに対応するものとして\NewDocumentCommandを紹介しています.従来の\renewcommandに対応するコマンドは\RenewDocumentCommandですが,ここでは説明を割愛します.また,他にも\newenvironmentに対応する\NewDocumentEnvironmentもあります.

*5:その他にも多くの指定子が存在します.詳しくは[4]をご覧ください.

*6:この例は[4]の1.4章から拝借しました.

*7:話の流れ上,2つとしましたが,個人的にはまだ注目すべき点はあります.先の表にある通り,「O」では入力がなかった場合のデフォルト値を設定できます.これは\newcommandでも使用できる機能です.注目したいのはそのデフォルト値に他の引数を利用できる点です.例えば2つ目の引数(O{#1ed})では,2つ目の入力が与えられなかった場合,デフォルト値の「[1番目の引数]ed」を格納します.\conjugate{walk}で言えば「walked」です.これの応用先はすぐには思い付きませんが,なんかすばらしそうな機能です.

*8:元々xparseパッケージを知ったきっかけは次の\newwordsというコマンドを作る方法を調べたことでした.「オプション引数の有無によって処理を変える方法」を探していた結果,\newcommandよりも便利なものを見つけたという次第です.

*9:ここで初めて登場した「\IfValueT」はオプション引数に対する入力の有無によって処理を変えるコマンドです.より詳しくは「\IfValueT{条件付けたい引数}{入力があればさせたい処理}」によって,「条件付けたい引数」に対応する引数に入力があれば「入力があればさせたい処理」を行うことができます.

*10:そうしないと見栄えが悪いので.

*11:条件の部分にarrayを使用しましたが,個人的にはgatheredの左詰めver.みたいな出力になることが理想で,まだ完全に納得している品物ではありません.→4章 追記 (2020.05.03) へ

*12:実はここまでの議論は本編を執筆していた段階で判明していたのですが,当時はその改善方法が分からなかったため,改善は後回しにしてとりあえず大体は出来ているからと公開してしまいました.

*13:おそらく偶数行だと駄目かもしれない.