LoginSignup
1
4

More than 3 years have passed since last update.

LaTeXでl3keysパッケージを用いてkey-value引数コマンドを定義する方法

Last updated at Posted at 2020-05-17

key-value引数とは?

LaTeXにおいてkey-value引数とは,次のようにコマンド引数を<key>=<value>の形で指定する引数のことです.

\includegraphics[width=5cm,height=3cm]{sample.jpg}

この記事では,このようなkey-value引数を扱えるコマンドの定義方法を紹介します.

key-value引数にすることのメリット

紹介の前にkey-value引数のメリットを伝えておきたいと思います.あくまでも個人的な感想ですが,共感した方はきっとこの記事が役に立つでしょう.

例えば

    \frac{\partial^nf(\boldsymbol{y})}{\partial y_i^n}

と出力される\derivativeというコマンドを定義したいとします.このとき,LaTeXに標準で備わっている\newcommandコマンドを使って次のような感じで定義するとします.

\newcommand{\derivative}[6]{...}

この\derivativeコマンドの仕様を,例えば

  • 第1引数:微分演算を作用させる関数の記号(上の例では $f$ )
  • 第2引数:微分される関数の変数の記号(上の例では $y$ )
  • 第3引数:微分の階数(上の例では $n$ )
  • 第4引数:変数の添え字の記号(上の例では $i$ )
  • 第5引数:微分演算子の記号(上の例では $\partial$ )
  • 第6引数:変数をボールド体に表示する場合はture,そうでなければfalseを指定(上の例ではtrue

と決めたとします.つまり

\derivative{f}{y}{n}{i}{\partial}{true}

と入力すると

    \frac{\partial^nf(\boldsymbol{y})}{\partial y_i^n}

と出力されるように\derivativeを定義します.

このように定義した場合,私達はコーディングの際,\derivativeの第何引数が何に対応するのかを記憶しておかなくてはなりません.また

    \frac{dg(x)}{dx}

と出力させたい場合は

\derivative{g}{x}{}{}{d}{false}

と入力しますが,このとき空の引数{}を省略することはできません.

もし,\derivativeをkey-value引数で定義したらどうでしょうか.ここで,次のようにkeyを定義したとします:

  • function:微分演算を作用させる関数の記号
  • variable:微分される関数の変数の記号
  • order:微分の階数
  • subscript:変数の添え字の記号
  • symbol:微分演算子の記号
  • multi:変数をボールド体で表示する場合はture,そうでなければfalseを指定

このとき,一番上の出力

    \frac{\partial^nf(\boldsymbol{y})}{\partial y_i^n}

は,次のように入力すればよい訳です.

\derivative{symbol=\partial,order=n,function=f,variable=y,multi=true,subscript=i}

また

    \frac{dg(x)}{dx}

と出力させたい場合は

\derivative{function=g,symbol=d,variable=x}

で済みます.

key-value引数で定義した場合は,すべての引数を指定する必要がなくなります1.しかも,引数の順番を気にする必要がなくなりますので,個人的にはコーディングが楽です.更に,入力コードと出力の対応がわかりやすいくデバッグもしやすいです.

そして,key-value引数にする最大のメリットは,コードの保守性を格段に高めることができる点でしょう.

例えば,次のように丸括弧 $()$ の出力を角括弧 $[]$ も出力できるように修正したくなったとします.

    \frac{\delta F[\rho]}{\delta \rho}

このとき\newcommandを用いて\derivativeを定義していた場合,2つの点で修正が必要になるでしょう.1点目は定義の修正です.例えば,新たに

  • 第7引数:角括弧で出力する場合はtrue,そうでなければfalseを指定

のように,引数の数を増やします.このとき,次のように定義し直せば良いでしょう:

\newcommand{\derivative}[7]{...}

修正点は引数の数を指定する[6]という記述を[7]に変更しています.また,具体的な定義内容{...}の箇所も適切に修正されているとします.つまり

    \frac{\delta F[\rho]}{\delta \rho}

と出力したい場合,次のように入力すれば良い訳です:

\derivative{F}{\rho}{}{}{\delta}{false}{true}

この\derivativeの定義の修正はそこまで時間は掛からないでしょう.

問題は2点目の修正です.そうです,実際に\derivativeが使われている本文コードの修正です.具体的には,今まで丸括弧 $()$ で出力していた箇所

\derivative{f}{y}{n}{i}{\partial}{true}

の記述にすべて

\derivative{f}{y}{n}{i}{\partial}{true}{false}

と第7引数{false}を追記する必要があります.この修正はすぐには済まないことは想像に難くないでしょう.

一方,key-value引数で\derivativeを定義していた場合を考えてみましょう.今まで丸括弧 $()$ で出力していた本文コードの\derivative修正する必要は一切ありません2

つまり,今まで

    \frac{dg(x)}{dx}

と出力していた入力コード

\derivative{function=g,symbol=d,variable=x}

を変更する必要はありません.

修正点は\derivativeの定義内容のみです.具体的には次のように,新たなkey

  • square-brackets:角括弧で出力する場合はtrue,そうでなければfalseを指定

を追加し,それに応じて定義内容も適切に修正します.つまり

\derivative{symbol=\delta,square-brackets=true,function=F,variable=\rho}

と入力すると

    \frac{\delta F[\rho]}{\delta \rho}

と出力されるように定義を修正します.それだけです.key-value引数の方がコードの保守性が高いことがおわかりいただけたでしょう.プログラミングにおいて保守性が高いことは非常に重要です.

ただし,デメリットとして入力文字数が増える傾向にありますが,今どきのエディタは補完機能があるのでそこは問題にならないでしょう.

l3keysパッケージについて

l3keysパッケージはLaTeX3 Project 3で開発されたパッケージです.次の記述で利用が可能になります:

\usepackage{expl3}

このパッケージの使い方はLaTeX2$\varepsilon$とはだいぶ異なり多少慣れが必要です.コマンド名にアンダースコア(_)やコロン(:)を使用します.

また,2020年5月17日現在,マニュアルに次のような記述がありますので,使用の際は頭の片隅に置いておいてください.

While expl3 is still experimental, the bundle is now regarded as broadly stable. The syntax conventions and functions provided are now ready for wider use. There may still be changes to some functions, but these will be minor when compared to the scope of expl3.

なお,この記事ではxparseパッケージを用いてコマンドを定義しますが,xparseパッケージは内部でexpl3パッケージを読み込んでいます.xparseの使い方はxparseパッケージでスゴイLaTeXマクロを作ろう!が参考になります.

l3keysパッケージの使い方

前置きが長くなりました.ここからkey-value引数を扱えるコマンドの定義方法を一つ一つ説明していきます.

予備知識

例1(keyの定義とセット)

前述した通り,expl3パッケージではコマンド名にアンダースコア(_)とコロン(:)を使用します.これを許可するために\ExplSyntaxOn\ExplSyntaxOffを記述する必要があります.つまり,これらで挟まれた領域は_:を文字として認識します.

ここで本題に入りますが,keyの定義は次のコマンドを使用します:

\keys_define:nn {<module>} {<keyval list>}

keyのセットは次のコマンドを使用します:

\keys_set:nn {<module>} {<keyval list>}

<module>の説明は後程しますが,次のコードとその出力の対応を見てみてください.

\documentclass{jarticle}
\usepackage{xparse}
\ExplSyntaxOn
\keys_define:nn { mymodule } {
    key .code:n = 値:#1
}
\ExplSyntaxOff
%
\begin{document}
\ExplSyntaxOn
\keys_set:nn { mymodule } { key = ABC }\\
\keys_set:nn { mymodule } { key = XYZ }\\
\keys_set:nn { mymodule } { key       }\\
\keys_set:nn { mymodule } {           }\textbf{END}
\ExplSyntaxOff
\end{document}

これは次のように出力されます:

    値:ABC\\値:XYZ\\値:\\\textbf{END}

ここで一番下のコマンド\keys_set:nn { mymodule } { }は何も出力されないことに注意してください.

例2(複数のkeyの定義)

複数keyを定義したい場合は次のように記述します:

\documentclass{jarticle}
\usepackage{xparse}
\ExplSyntaxOn
\keys_define:nn { mymodule } {
    key-one .code:n = 値1:#1,
    key-two .code:n = 値2:#1
}
\ExplSyntaxOff
%
\begin{document}
\ExplSyntaxOn
\keys_set:nn { mymodule } { key-one = A }\\
\keys_set:nn { mymodule } { key-two = B }\\
\keys_set:nn { mymodule } { key-one = P, key-two = Q }\\
\keys_set:nn { mymodule } { key-two = X, key-one = Y }
\ExplSyntaxOff
\end{document}

これは次のように出力されます:

    値1:A\\値2:B\\値1:P値2:Q\\値2:X値1:Y

この出力から記述の無かったkeyはセットされないことがわかります.また,3番目と4番目から,keyの記述順も考慮されることがわかります.

例3(デフォルト値)

\documentclass{jarticle}
\usepackage{xparse}
\ExplSyntaxOn
\keys_define:nn { mymodule } {
    key .code:n    = 値:#1,
    key .default:n = デフォルト
}
\ExplSyntaxOff
%
\begin{document}
\ExplSyntaxOn
\keys_set:nn { mymodule } { key = ABC }\\
\keys_set:nn { mymodule } { key =     }\\
\keys_set:nn { mymodule } { key       }\\
\keys_set:nn { mymodule } {           }\textbf{END}
\ExplSyntaxOff
\end{document}

これは次のように出力されます:

    値:ABC\\値:\\値:デフォルト\\\textbf{END}

\keys_set:nn { mymodule } { key }のようにkeyのみ指定するとデフォルト値がセットされます.
また,4番目からkeyの指定が無い場合は,そもそもセットされないことがわかります.

例4(Moduleの役割)

module名でkeyをグループ分けしています.

\documentclass{jarticle}
\usepackage{xparse}
\ExplSyntaxOn
\keys_define:nn { mymodule1 } { mykey .code:n = 値1:#1 }
\keys_define:nn { mymodule2 } { mykey .code:n = 値2:#1 }
\ExplSyntaxOff
%
\begin{document}
\ExplSyntaxOn
\keys_set:nn { mymodule1 } { mykey = ABC }\\
\keys_set:nn { mymodule2 } { mykey = XYZ }
\ExplSyntaxOff
\end{document}

これは次のように出力されます:

    値1:ABC\\値2:XYZ

このように同じmykeyというkeyでも,属するmodule名が異なっているので区別されます.

key-value引数の定義方法

以上を踏まえてkey-value引数を扱えるコマンドを定義します.

例5(シンプルな定義)

\documentclass{jarticle}
\usepackage{xparse}
\ExplSyntaxOn
\keys_define:nn { mymodule } {
    vocation .code:n = \textbf{#1}
}
\NewDocumentCommand \mycommand { o m }{
    \group_begin:
    \keys_set:nn { mymodule } { #1 }
    :「{#2}\group_end:
}
\ExplSyntaxOff
%
\begin{document}
\mycommand[vocation=勇者]{(勇者は村人たちに話しかけた)}\\
\mycommand[vocation=村人]{ここラダトームは たくさんの人々が あつまる 楽園でした。}
\end{document}

これは次のように出力されます:

    \textbf{勇者}:「(勇者は村人たちに話しかけた)」\\
    \textbf{村人}:「ここラダトームは たくさんの人々が あつまる 楽園でした。」

例6(デフォルト値)

\documentclass{jarticle}
\usepackage{xparse}
\ExplSyntaxOn
\keys_define:nn { mymodule } {
    vocation .code:n    = \textbf{#1},
    vocation .default:n = 村人,
    id       .code:n    = \textbf{#1},
    id       .default:n = A,
}
\NewDocumentCommand \mycommand { o m } {
    \group_begin:
    \keys_set:nn { mymodule } { #1 }
    :「{#2}\group_end:
}
\ExplSyntaxOff
%
\begin{document}
\mycommand[vocation=勇者]{(勇者は村人たちに話しかけた)}\\
\mycommand[vocation]{ここラダトームは たくさんの人々が あつまる 楽園でした。}\\
\mycommand[vocation,id=B]{それを まものたちが...。 うっ うっ うっ...。}\\
\mycommand[vocation,id=C]{私は 旅の商人です。 これまでにも 多くの仲間が まものたちに 殺されました。}
\end{document}

これは次のように出力されます:

    \textbf{勇者}:「(勇者は村人たちに話しかけた)」\\
    \textbf{村人}:「ここラダトームは たくさんの人々が あつまる 楽園でした。」\\
    \textbf{村人B}:「それを まものたちが...。 うっ うっ うっ...。」\\
    \textbf{村人C}:「私は 旅の商人です。 これまでにも 多くの仲間が まものたちに 殺されました。」

例7(初期値)

\documentclass{jarticle}
\usepackage{xparse}
\ExplSyntaxOn
\keys_define:nn { mymodule } {
    vocation .code:n    = \tl_set:Nn \l_vocation_tl {#1},
    vocation .initial:n = 村人,
    id       .code:n    = \tl_set:Nn \l_id_tl {#1},
    id       .initial:n = A,
}
\NewDocumentCommand \mycommand { O{} m } {
    \group_begin:
    \keys_set:nn { mymodule } { #1 }
    \textbf{\l_vocation_tl\l_id_tl}:「#2」
    \group_end:
}
\ExplSyntaxOff
%
\begin{document}
\mycommand[vocation=勇者]{(勇者は村人たちに話しかけた)}\\
\mycommand{ここラダトームは たくさんの人々が あつまる 楽園でした。}\\
\mycommand[id=B]{それを まものたちが...。 うっ うっ うっ...。}\\
\mycommand[vocation=魔物, id=X]{なにゆえもがき生きるのか? ほろびこそわがよろこび。 死にゆく者こそ美しい。 さあ わがうでの中で息絶えるがよい!}
\end{document}

これは次のように出力されます:

    \textbf{勇者A}:「(勇者は村人たちに話しかけた)」\\
    \textbf{村人A}:「ここラダトームは たくさんの人々が あつまる 楽園でした。」\\
    \textbf{村人B}:「それを まものたちが...。 うっ うっ うっ...。」\\
    \textbf{魔神X}:「なにゆえもがき生きるのか? ほろびこそわがよろこび。 死にゆく者こそ美しい。 さあ わがうでの中で息絶えるがよい!」

2番目からkeyの指定が無くても値がセットされていることがわかります.この点がデフォルト値(.default:n)と異なります.

例8(Boolean値)

\documentclass{jarticle}
\usepackage{xparse}
\ExplSyntaxOn
\keys_define:nn { mymodule } {
    vocation .code:n     = \tl_set:Nn \l_vocation_tl {#1},
    vocation .initial:n  = 村人,
    id       .code:n     = \tl_set:Nn \l_id_tl {#1},
    id       .initial:n  = A,
    bold     .bool_set:N = \l_bold_bool,
}
\NewDocumentCommand \mycommand { O{} m } {
    \group_begin:
    \keys_set:nn { mymodule } { #1 }
    {\bool_if:NT\l_bold_bool{\bfseries}\l_vocation_tl\l_id_tl}:「#2」
    \group_end:
}
\ExplSyntaxOff
\begin{document}
\mycommand[vocation=勇者,bold=true,id=]{(勇者は村人たちに話しかけた)}\\
\mycommand{ここラダトームは たくさんの人々が あつまる 楽園でした。}\\
\mycommand[id=B,bold]{それを まものたちが...。 うっ うっ うっ...。}\\
\mycommand[vocation=魔物, id=X]{なにゆえもがき生きるのか? ほろびこそわがよろこび。 死にゆく者こそ美しい。 さあ わがうでの中で息絶えるがよい!}
\end{document}

これは次のように出力されます:

    \textbf{勇者}:「(勇者は村人たちに話しかけた)」\\
    村人A:「ここラダトームは たくさんの人々が あつまる 楽園でした。」\\
    \textbf{村人B}:「それを まものたちが...。 うっ うっ うっ...。」\\
    魔神X:「なにゆえもがき生きるのか? ほろびこそわがよろこび。 死にゆく者こそ美しい。 さあ わがうでの中で息絶えるがよい!」

例9(環境への適用)

次のように,環境へのkey-value引数も定義できます:

\keys_define:nn { mymodule } {
    color .code:n    = \tl_set:Nn \l_color_tl {#1},
    color .initial:n = blue,
    font  .code:n    = \tl_set:Nn \l_font_tl {#1},
    font  .initial:n = \ttfamily,
}
\NewDocumentEnvironment {myenvironment} { O{} } {
    \keys_set:nn { mymodule } { #1 }
    \color{\l_color_tl}\l_font_tl
}{}

使用例:

\begin{myenvironment}[font=\sffamily,color=red]
    死してなお消えぬほどの永遠の恐怖をその魂に焼きつけてくれるわっ!!
\end{myenvironment}

最後に

冒頭で言及したkey-value引数版\derivativeの定義を載せておきます:

\keys_define:nn { derivative } {
    function        .code:n     = \tl_set:Nn \l_function_tl { #1 },
    function        .initial:n  = f,
    multi           .bool_set:N = \l_multi_bool,
    order           .code:n     = \tl_set:Nn \l_order_tl { #1 },
    order           .initial:n  = ,
    square-brackets .bool_set:N = \l_square_brackets_bool,
    subscript       .code:n     = \tl_set:Nn \l_subscript_tl { #1 },
    subscript       .initial:n  = ,
    symbol          .code:n     = \tl_set:Nn \l_symbol_tl { #1 },
    symbol          .initial:n  = d,
    variable        .code:n     = \tl_set:Nn \l_variable_tl { #1 },
    variable        .initial:n  = x,
}
\NewDocumentCommand \derivative { O{} } {
    \group_begin:
    \keys_set:nn { derivative } { #1 }
    \frac{
        \l_symbol_tl^{\l_order_tl}\l_function_tl
        \bool_if:NTF\l_square_brackets_bool{[}{(}
        \bool_if:NTF\l_multi_bool{\boldsymbol{\l_variable_tl}}{\l_variable_tl}
        \bool_if:NTF\l_square_brackets_bool{]}{)}
    }{
        \l_symbol_tl\l_variable_tl\c_math_subscript_token{\l_subscript_tl}^{\l_order_tl}
    }
    \group_end:
}

このように定義された\derivativeについて

\derivative, \derivative[order=n,multi,function=h,variable=y,subscript=i,symbol=\partial], \derivative[symbol=\delta,function=F,square-brackets,variable=\rho]

は次のように出力されます:

    \frac{df(x)}{dx}, \frac{\partial^nh(\boldsymbol{y})}{\partial y_i^n}, \frac{\delta F[\rho]}{\delta\rho}

なお,\ExplSyntaxOn\ExplSyntaxOffで挟まれた領域では,_は普通の文字として扱われるため数式の下添え字を書くためには\c_math_subscript_tokenを使って定義する必要があります.


  1. 厳密に言えば,コマンドの定義に依ります.また,初期値を指定した場合,更に省略が可能です. 

  2. こちらも,厳密に言えば,コマンドの定義に依ります. 

  3. 次世代のLaTeXタイプセットシステムを開発しているプロジェクトらしいです. 

1
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
4