\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 の効果が、{} の中だけに影響していますね。

この「影響範囲」というのは、\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 を実現するは以下のような手順を踏めばよいでしょう。

  1. 局所化を開始する
  2. \ExpandArgs を使って文字列を命令化する
  3. 局所化を終了する
  4. 命令化した文字列を使う

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 で頑張りましょう。