\UseName
でもエラーが出したい!
thth
2022-12-08 (updated 2022-12-26)
これは、「TeX & LaTeX Advent Calendar 2022」の 6 日目の記事『\NewDocumentCommand
さえあればいい。』を投稿した後に思いついた話です。
この記事単体でも読めるようになっています。
これ自体は 8 日目の記事です。 昨日は『TikZ で名刺作り』(CareleSmith9 さん)で、明日は『ZR🙃的TeXまめちしき』(zr_tex8r さん)です。
\newcommand
、\newenvironment
の使い方はわかっている前提です。
TeX Live 2022 以降の LaTeX を前提としています。 コードブロックではプリアンブルなどは省略します。 適宜補ってください。
この記事は CC BY-SA 4.0、コードの部分はパブリックドメイン・無保証です。
\UseName
とは
\UseName{⟨命令名⟩}
とすると、\⟨命令名⟩
に置き換えられます。
たとえば \UseName{newcommand}
が \newcommand
になるといった具合です。
このように命令化した ⟨文字列⟩
を ⟨別の命令⟩
に渡したいときは、\ExpandArgs{c}⟨別の命令⟩{⟨文字列⟩}
とすると ⟨別の命令⟩\⟨文字列⟩
に置き換えられるのでした。
先日の記事では、「このように文字列から作られた命令が未定義だった場合、エラーが出ない」という副作用があるということを説明しました。
% \Undefined は未定義
\UseName{Undefined}% エラーでない
\Undefined% エラーでない
この記事ではこの挙動を改善し、「命令が未定義であった場合にはエラーを出すバージョンの \UseName
」として \MyUseName
を作っていきます。
{}
と局所化
{}
を見ると「命令に引数を渡すときのカッコ」だと思う人が多いでしょうが、実は、それ以外の用途があるのをご存知ですか?
「引数の範囲を示す」以外の場面で使われると、どのような意味を持つのでしょう。
{} というカッコは波カッコとか中カッコとか呼ばれる。
上のコードを実行してみると、{}
という文字は出力されずに消えてしまいました。
実は、{}
というカッコは、「引数の範囲を示す」以外の場面では「影響範囲を示す」という効果を持ちます。
{\Large 大きい文字}と普通の文字
文字を大きくして出力する命令である \Large
の効果が、{}
の中だけに影響していますね。
この「影響範囲」というのは、\Large
などの組版結果に直接影響を及ぼす命令に限りません。
\newcommand
や \NewDocumentCommand
などの「命令の定義を変える」ような命令の影響も、{}
の中だけに制限されます。
\newcommand{\mycommand}{foo}
\mycommand % -> foo
{
\mycommand % -> foo
\renewcommand{\mycommand}{bar}
\mycommand % -> bar
}
\mycommand % -> foo
賢明なる読者の方はお気づきでしょうが、\UseName
や \ExpandArgs
の副作用が影響する範囲も {}
を使うと限定することができます。
% \Undefined は未定義
{
\UseName{Undefined}% エラーでない
\Undefined % エラーでない
}
\Undefined % エラーでる!
このように効果範囲を限定することを、この記事では「局所化する」ということにします。
以上を踏まえると、\MyUseName
を実現するは以下のような手順を踏めばよいでしょう。
- 局所化を開始する
\ExpandArgs
を使って文字列を命令化する- 局所化を終了する
- 命令化した文字列を使う
LaTeX が提供する命令には、局所化の影響を受けないものもあります。
たとえば \setcounter
や \stepcounter
がこれにあたります。
もちろん、\setlength
など局所化の影響を受ける命令もあります。
\newlength{\mylength}
\skipeval{\mylength} % -> 0.0pt
\newcounter{mycounter}%
\themycounter% -> 0
{
\setlength{\mylength}{1pt}
\skipeval{\mylength} % -> 1.0pt
\setcounter{mycounter}{1}%
\themycounter% -> 1
}
\skipeval{\mylength} % -> 0.0pt
\themycounter% -> 1
局所化をバラバラに
\MyUseName
の実装方針が決まったので、なにも考えずに {}
を使ってとりあえずこんなコードを書いてみました:
\newcommand{\MyUseName}[1]{%
{% 局所化の開始
\ExpandArgs{c}\MyUseNameI{#1}%
}% \MyUseName の定義の終わり
\newcommand{\MyUseNameI}[1]{%
}% 局所化の終了
#1%
}% \MyUseNameI の定義の終わり
もちろんこれは期待通りに動作しません。
引数の中に含まれる {}
の数は釣り合ってなければいけないので、上のコードでは以下の部分が \MyUseName
の定義だということになります。
{% 局所化の開始
\ExpandArgs{c}\MyUseNameI{#1}%
}% \MyUseName の定義の終わり
\newcommand{\MyUseNameI}[1]{%
}% 局所化の終了
#1%
{}
で局所化している限りでは、この問題は解決できなさそうです。
{}
とは違い、引数に入れるときにバラバラにしても大丈夫で、局所化できるようなもの……、ありますよね。
それは、「環境」です。
なにもしない Empty
環境を作ってみて、その効果を確かめてみましょう。
\newenvironment{Empty}{}{}
\newcommand{\mycommand}{foo}
\mycommand % -> foo
\begin{empty}
\mycommand % -> foo
\renewcommand{\mycommand}{bar}
\mycommand % -> bar
\end{empty}
\mycommand % -> foo
\renewcommand
が環境の中だけに影響しているのがわかります。
{}
の代わりに \begin{Empty}\end{Empty}
を使うことにして、先ほどの \MyUseName
を書き直します。
\newenvironment{Empty}{}{}
\newcommand{\MyUseName}[1]{%
\begin{Empty}% 局所化の開始
\ExpandArgs{c}\MyUseNameI{#1}%
}
\newcommand{\MyUseNameI}[1]{%
\end{Empty}% 局所化の終了
#1%
}
それでは試してみましょう。
\MyUseName{Undefined}
% \MyUseName を定義に置き換える
% #1 <- Undefined
\begin{Empty}% 局所化開始
\ExpandArgs{c}\MyUseNameI{Undefined}
% \ExpandArgs を置き換える
% c <- Undefined
% \Undefined が副作用によりエラー出なくなる
\MyUseNameI\Undefined
% \MyUseNameI を置き換える
% #1 <- \Undefined
\end{Empty}% 局所化終了
% \Undefined の副作用の効果なくなる
\Undefined
無事に、! Undefined control sequence.
のエラーが出ましたね。
めでたしめでたし。
引数に渡す場合
今度は \ExpandArgs
のように、別の命令の引数に渡す場合を考えてみます。
といっても \MyUseName
の定義を少し変えるだけです。
\newenvironment{Empty}{}{}
\newcommand{\MyUseName}[2][]{%
\begin{Empty}%
\ExpandArgs{c}\MyUseNameI{#2}{#1}%
}
\newcommand{\MyUseNameI}[2]{%
\end{Empty}%
#2#1%
}
\MyUseName
にオプション引数をつけました。
これで、\MyUseName[⟨別の命令⟩]{⟨文字列⟩}
とすると ⟨別の命令⟩\⟨文字列⟩
に置き換えられるようになります。
⟨別の命令⟩
は単体の命令である必要はなく、引数などをつけたもの(たとえば \newcommand*
)だったとしても構いません。
ただし、[]
のカッコを含む場合は引数全体を {}
で包む必要があります(詳しくは本編へ)。
おわり
普通の LaTeX ユーザであれば、うっかりスペルミスをしてもエラーが出てくれる \MyUseName
の方が使い勝手がよいのではないでしょうか。
なぜもともとの \UseName
や \ExpandArgs
の実装がこんなことになっているのかというと、それには TeX 言語やら expl3 やらの話が関係してきます。
TeX 言語や expl3 に手を出すつもりのない人は、諦めるか \MyUseName
と \NewDocumentCommand
で頑張りましょう。