6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

徹底解説! マクロの等価性を理解する

Last updated at Posted at 2022-12-24

これは「TeX & LaTeX Advent Caleandar 2022」の25日目の記事です。
(24日目は golden_lucky さんです。)

​「やっぱりTeX言語(とか)しましょう」​を重点テーマとする「TeX & LaTeX Advent Calendar 2022」もいよいよクリスマス:christmas_tree:の日を迎えることになりました。最終日となる今日は​「TeX言語におけるマクロの等価性」​という少しニッチな話題を扱うことにします。

前提知識

というわけで、これはガチTeX言語者:sushi:向けの記事です。幸せなLaTeXユーザ:blush:の皆さんは例によってアドベントカレンダーのフィナーレを平和に待つことにましょう:christmas_tree::snowman:

本記事の「キホンの話」までの内容については以下の知識を仮定します。

  • TeX言語の「トークン(token)」「マクロ(macro)」についてのキホン的な知識。
  • \ifxが何をするものか」についての概略。

そこより先の内容については、さらに以下の知識を仮定します。

  • 「文字トークンのカテゴリコード」についてのキホン的な知識。

復習:book:の話

TeX言語における「トークンの等価性」を判定する条件文(if-トークン)には\if\ifcat\ifxの3つがありますが、その中でも使う機会が一番多いのは\ifxでしょう。この\ifxは、2つのトークンの意味が等しいかを判定するif-トークンです。

  • \ifx«トークン1»«トークン2»:〔if-トークン〕 «トークン1»«トークン2»意味が同じであるか否か。

ここで、比較対象の2つのトークンの現在の意味がともにマクロ(つまり“マクロ型の値”)である場合は、「2つのマクロの等価性」が問題になります。この記事では、TeX言語においてマクロの等価性がどう決められているのかについて解説します。

準備:pencil2:の話

2つのトークンの等価性を判定して結果を表示するマクロを用意しておきましょう。

%% \Check<トークン1><トークン2>
% 2つのトークンの意味が等しいなら"SAME"、そうでないなら"DIFFERENT"
% を端末に表示する。
\def\Check#1#2{%
  \immediate\write16{\ifx#1#2SAME\else DIFFERENT\fi}}

この\Checkマクロは以下のように使います。

\Check AA %==>SAME
\Check AB %==>DIFFERENT

以降に示す例においては、実際に\Checkを実行したときの結果を上記の形式で書くことにします。

キホン:slight_smile:の話

まずは、フツーのTeX言語の参考書に載っているレベルの説明から始めましょう1

2つのマクロは「次の3要素の全てについて等しい」場合に等価となります。

  • 置換テキスト(replacement text)
  • パラメタテキスト(parameter text)
  • 接頭辞(prefix)の設定状態

これらの用語に聞きなじみのない人もいると思われるので、ちょっと補足しておきましょう。

\long\def\macroA#1;#2{#2-#1}

「置換テキスト」というのは“マクロの定義の本体”のトークン列のことで、上掲の\macroAの定義では#2-#1となります。また「パラメタテキスト」は“引数の書式指定”のトークン列のことで、この例では#1;#2となります2。「接頭辞の設定状態」はこの例では「\long付」となります。ここで考慮対象となるマクロの接頭辞には元祖TeXの範囲では\long\outerがあり、e-TeX拡張においてはこれに\protectedが追加されます3\globalについては後述)。

等価になる場合

最も自明なのは2つのマクロに対する定義文4が全く同じ場合です。当然“3要素”が全て同じになるので、2つのマクロは等価になります。

% パラメタテキスト=“#1;#2”
% 置換テキスト=“#2-#1”
% 接頭辞設定状態=“\long 付”
\long\def\macroA#1;#2{#2-#1}
\long\def\macroB#1;#2{#2-#1}
\Check\macroA\macroB %==>SAME

また、定義文のソースコード上での文字列が異なっていても、結局それらが同じトークン列に解釈されるのであれば、やはり等価になります。

% パラメタテキスト=“#1\and #2.”
% 置換テキスト=“#1␣\&␣#2\par”
% 接頭辞設定状態=“なし”
\def\macroA#1\and#2.{#1 \& #2\par}
% 行末は空白文字トークン、空行は \par トークンになるので
% 結局 \macroA と同じ定義文になる。
\def\macroB  #1\and  #2.{%
  #1
  \&
  #2%

}
\Check\macroA\macroB %==>SAME

接頭辞設定状態は「マクロ定義時に各接頭辞が付いていたか否か」のみで区別されるので、定義文における接頭辞の出現の順序は考慮されません。

% パラメタテキスト=“#2-#1”
% 置換テキスト=“#1;#2”
% 接頭辞設定状態=“\protected・\long 付”
\long\protected\def\macroA#1;#2{#2-#1}
\protected\long\def\macroB#1;#2{#2-#1}
\Check\macroA\macroB %==>SAME

等価にならない場合

“3要素”の何れかの点で2つのマクロが異なる場合は等価になりません。

パラメタテキストが異なる例です。

%↓パラメタテキスト=“#1;#2”
\def\macroA#1;#2{#2-#1}
%↓パラメタテキスト=“#1,#2”
\def\macroB#1,#2{#2-#1}
\Check\macroA\macroB %==>DIFFERENT

置換テキストが異なる例です。

%↓置換テキスト=“#2A#1”
\def\macroA#1;#2{#2A#1}
%↓置換テキスト=“#2B#1”
\def\macroB#1;#2{#2B#1}
\Check\macroA\macroB %==>DIFFERENT

接頭辞設定状態が異なる例です。

%↓接頭辞設定状態=“\protected・\long 付”
\protected\long\def\macroA#1;#2{#2-#1}
%↓接頭辞設定状態=“\long 付”
          \long\def\macroB#1;#2{#2-#1}
\Check\macroA\macroB %==>DIFFERENT

やっぱり等価にならない場合

パラメタテキストや置換テキストに対して「トークン列の一致」を判断する基準は “外形” です。以下の例では\RELAX\relaxと同じ意味をもちますが異なる制御綴である(つまり“外形”が異なる)ので、\macroA\macroBの置換テキストは同一とは見なされません。

\let\RELAX\relax
%↓置換テキスト=“#2\relax #1”
\def\macroA#1;#2{#2\relax#1}
%↓置換テキスト=“#2\RELAX #1”
\def\macroB#1;#2{#2\RELAX#1}
%“\relax”と“\RELAX”は外形が異なるので、2つのマクロの置換テキストは異なる。
\Check\macroA\macroB %==>DIFFERENT

また「トークン列の一致」を判断する際には展開は行われません。以下の例では、\RELAX\RELAXは完全展開すると確かに\relax\relaxになるはずですが、それでも\macroA\macroBの置換テキストは異なると見なされます。

\def\RELAX{\relax}
%↓置換テキスト=“\relax \relax”
\def\macroA#1;#2{\relax\relax}
%↓置換テキスト=“\RELAX \RELAX”
\def\macroB#1;#2{\RELAX\RELAX}
% 展開せずに比較するので、2つのマクロの置換テキストは異なる。
\Check\macroA\macroB %==>DIFFERENT

なにげに等価になる場合

先ほどの説明ではマクロの接頭辞には\protected\long\outerの3つがあると述べました。実はマクロの定義についてはもう1つ、\globalという接頭辞も存在します。しかしこの\globalは定義の実行の様態(定義を大域的に行う)を表すものであるため、定義されるマクロ自体の状態としては反映されません。従って、定義文における\globalの有無5はマクロの等価性に影響しません。

%↓接頭辞定義状態=“なし”
       \def\macroA#1;#2{#2-#1}
%↓\global は反映されないので接頭辞定義状態は“なし”となる。
\global\def\macroB#1;#2{#2-#1}
\Check\macroA\macroB %==>SAME
% \gdef は \global\def と等価なのでこれも同じ結果になる。
\gdef\macroB#1;#2{#2-#1}
\Check\macroA\macroB %==>SAME

定義文ではなくマクロ自体の中身のみが考慮されるため、マクロを\defで定義したか\edefで定義したかも(それ自体は)考慮されません。結果的に同じ置換テキストに帰着するのであれば、それらは等価と見なされます。

\def\RELAX{\relax}
%↓置換テキスト=“\relax \relax”
\def\macroA#1;#2{\relax\relax}
%↓置換テキストは完全展開されるので“\relax \relax”となる。
\edef\macroB#1;#2{\RELAX\RELAX}
% 結果的に両者の置換テキストは一致する。
\Check\macroA\macroB %==>SAME

次の例は先の例と似ていますが、\letで等価定義した場合は「\relaxに展開される」わけではないので両者は異なる結果になります。

\let\RELAX\relax
%↓置換テキスト=“\relax \relax”
\def\macroA#1;#2{\relax\relax}
%↓\RELAX は展開不能なので置換テキストはそのまま“\RELAX \RELAX”となる。
\edef\macroB#1;#2{\RELAX\RELAX}
\Check\macroA\macroB %==>DIFFERENT
% もちろん後で \RELAX を展開可能に変えても \macroB 自体は何も変わらない。
\def\RELAX{\relax}
\Check\macroA\macroB %==>DIFFERENT

チョットアレ:sushi:な話

カテゴリコードが絡む場合

トークン列の“外形”の一致を判断する際には、文字トークンについては文字コードとカテゴリコードの両方が考慮されます。そのため、文字コードが同じでもカテゴリコードが異なる場合は等価になりません。

本記事では「カテゴリコードが11の@の文字トークン」のことを@₁₁のように表記します。これはTeX言語の参考書で一般的に用いられる表記法です。

%↓置換テキスト=“#2@₁₁#1”
\catcode`\@=11 \def\macroA#1;#2{#2@#1}
%↓置換テキスト=“#2@₁₂#1”、“@”のカテゴリコードが異なる。
\catcode`\@=12 \def\macroB#1;#2{#2@#1}
\Check\macroA\macroB %==>DIFFERENT

上掲の例で\macroA\macroB\meaningを適用して得られる文字列はともに
macro:#1;#2->#2@#1
となり一致します(“\the-文字列化”する際にカテゴリコードの違いは消えてしまうため)。この例から「2つのトークンについて、その\meaningの文字列が一致していてもそれらが等価であるとは限らない」ということがわかります。

もちろん、カテゴリコードが同じで文字コードが異なる場合も同様です。

\catcode`\<=1 \catcode`\>=2
%↓置換テキスト=“#2-₁₂\foo {₁#1}₂”
\def\macroA#1;#2{#2-\foo{#1}}
%↓置換テキスト=“#2-₁₂\foo <₁#1>₂”、文字コードが異なる。
\def\macroA#1;#2{#2-\foo<#1>}
\Check\macroA\macroB %==>DIFFERENT

このように文字トークンの“外形”には文字コードとカテゴリコードの両方の情報が含まれますが、一方で、制御綴の“外形”をみる際に「その制御綴の表記中のエスケープ文字が何であったか」は考慮されません6。従って、以下の例において\?!?は“外形において等価”と見なされ、結果的に2つのマクロが等価になります。

TeX言語の参考書では制御綴のトークンを $\fbox{relax}$ のような枠付の文字列で表現することもあります。こちらの表現のほうが「制御綴の外形はエスケープ文字を含まない」ことが理解しやすいかもしれません。

\catcode`\!=0 %“!”をエスケープ文字にする
%↓置換テキスト=“#2\?\foo {#1}”
\def\macroA#1;#2\relax{#2\?\foo{#1}}
%↓“\?”と“!?”はそもそも区別されないので置換テキストは \macroA と同じになる。
\def\macroB#1;#2!relax{#2!?!foo{#1}}
\Check\macroA\macroB %==>SAME

なお、定義文におけるカテゴリコードや文字コードの違いがマクロ自体に影響しない場合もあります。例えば、以下の例では、定義文において{₁}₂を使ったか<₁>₂を使ったかは置換テキストには(その他の要素にも)反映されないので、2つのマクロは等価になります。

\catcode`\<=1 \catcode`\>=2
%↓全体を囲む { } 自体は置換テキストには含まれないので置換テキストは“#2-₁₂#1”。
\def\macroA#1;#2{#2-#1}
%↓こちらも < > 自体は置換テキストには含まれないので置換テキストは“#2-₁₂#1”。
\def\macroB#1;#2<#2-#1>
\Check\macroA\macroB %==>SAME

もちろん、「波括弧区切り(brace delimited)」にした場合7はその波括弧がパラメタテキストと置換テキストに含まれることになります。

\catcode`\<=1 \catcode`\>=2
%↓パラメタテキスト=“#1;₁₂#2{₁”;置換テキスト=“#2-₁₂#1{₁”
\def\macroA#1;#2#{#2-#1}
%↓パラメタテキスト=“#1;₁₂#2<₁”;置換テキスト=“#2-₁₂#1<₁”
\def\macroB#1;#2#<#2-#1>
\Check\macroA\macroB %==>DIFFERENT

パラメタトークンが絡む場合

先ほど、文字トークンの外形の一致では文字コードとカテゴリコードの両方が考慮されると述べました。ところがこれには一見例外に思えるものがあります。次のように、置換テキストにおいて文字コードが異なるパラメタ文字トークンを含めた場合、その文字コードの違いは考慮されないのです:astonished:

パラメタ文字トークン(parameter character token)」は「カテゴリコードが6である文字トークン」を指します。後で紹介する「パラメタトークン(parameter token)」と混同しないように注意してください。

\catcode`\&=6 % パラメタ文字にする
%↓置換テキストは“#₆2₁₂-₁₂#₆1₁₂”のはず…。
\def\macroA#1;#2{#2-#1}
%↓置換テキストは“#₆2₁₂-₁₂&₆1₁₂”のはず…。
\def\macroB#1;#2{#2-&1}
% なのになぜか等価であると判定される!
\Check\macroA\macroB %==>SAME

なぜパラメタ文字トークンだけが例外になるのかというと、その理由は「マクロのパラメタテキストや置換テキストの中においては、#1のようなマクロパラメタは特殊扱いになっている」からです。具体的には、#1のようなマクロパラメタの部分はそのまま#₆1₁₂として保存されるのではなく、パラメタトークンと呼ばれる特殊8なトークンに置き換えられた形で保存されます。

ただしパラメタトークンは一種類だけ存在するのではありません。#1#2は異なるパラメタを表すのでこの違いは保持されないといけません。このため、置換テキスト中のパラメタトークンは「#1に相当するもの」から「#9に相当するもの」までの9種類が存在しています。これを1ₐ9ₐで表すことにしましょう9

そうすると、置換テキストのトークン列の保存の際には
#₆1₁₂1ₐ#₆2₁₂2ₐ、……、#₆9₁₂9ₐ
という置き換えが発生することになります。ここで、パラメタトークンには「元のパラメタ文字トークン(#₆など)の文字コードが何であったか」の情報は残っていないことに注意しましょう。すると、例えば&₆のような「別の文字コードのパラメタ文字トークン」を用いたとしても
&₆1₁₂1ₐ&₆2₁₂2ₐ、……、&₆9₁₂9ₐ
と置き換えられることになります。

これを踏まえた上で、もう一度先の例を見てみましょう。

\catcode`\&=6 % パラメタ文字にする
\def\macroA#1;#2{#2-#1}
\def\macroB#1;#2{#2-&1}

パラメタトークンでの置き換えを考慮すると以下のようになり、両者の置換テキストは一致します。

  • \macroAの置換テキストは#₆2₁₂-₁₂#₆1₁₂2ₐ-₁₂1ₐ
  • \macroBの置換テキストは#₆2₁₂-₁₂&₆1₁₂2ₐ-₁₂1ₐ

結局、「置換テキスト中のパラメタ文字トークンの文字コードの違い」はマクロの等価性には影響しないことがわかりました。

ところで、TeX言語のマクロ定義の文法では、パラメタ文字トークンは暗黙の文字トークン10も許容されることになっています(参考記事)。パラメタ文字トークンを暗黙にすることはマクロの等価性に影響するのでしょうか。実際に調べてみましょう。

\let\HASH=# % 暗黙のパラメタ文字トークン
%↓置換テキスト=“2ₐ-₁₂1ₐ”
\def\macroA#1;#2{#2-#1}
%↓やっぱり置換テキストは“2ₐ-₁₂1ₐ”となる。
\def\macroB#1;#2{#2-\HASH1}
\Check\macroA\macroB %==>SAME

あくまで「1番目のパラメタ」を表すパラメタトークンは1ₐしか存在しないので、「元のパラメタ文字トークンが暗黙か明示か」の情報も残らないのでした。

超絶アレ:sushi::sushi::sushi:な話

前節では「置換テキストに文字コードの異なるパラメタ文字トークンを使う」話をしました。それでは、パラメタテキストに文字コードの異なるパラメタ文字トークンを使った場合はどうなるでしょう。フツーに考えると置換テキストの場合と同じ結果になりそうですが念のため確認してみましょう……。

\catcode`\&=6 % パラメタ文字にする
%↓パラメタテキストは“1ₐ;₁₂2ₐ”のはず…。
\def\macroA#1;#2{#2-#1}
%↓パラメタテキストは“1ₐ;₁₂2ₐ”のはず…。
\def\macroB#1;&2{#2-#1}
% なのになぜか等価でないと判定される!
\Check\macroA\macroB %==>DIFFERENT

アレレ、置換テキストの場合と異なり、マクロが等価でないと判定されてしまいました。なんということでしょう:astonished::astonished::astonished:

なぜこんなことになるかというと、同じ“パラメタトークン”といってもパラメタテキスト用と置換テキスト用ではその扱いが異なっているから、というのがその理由です。恐らく、内部の実装の都合でそうしているのがマクロの等価性という性質を通して“微妙に外に漏れてしまっている”というアレ:sushi:な事案なのでしょう:frowning:

パラメタテキスト用のパラメタトークンが置換テキスト用のそれと異なる点は​「文字コードを保持している」​ことです。つまり(符号空間が8ビットである元祖TeXでいうと)「文字コードが0のもの」から「文字コードが255のもの」までの256種類が存在するわけです。そこで、例えばXの文字コードをもつ(パラメタテキスト用の)パラメタトークンをXₚで表すことにしましょう11。パラメタテキストのトークン列中でのマクロパラメタは以下のように置き換えられます。
X₆1₁₂XₚX₆2₁₂Xₚ、……、X₆9₁₂Xₚ (Xは任意の文字)
おや、パラメタの番号(1~9)の情報が消えてしまっています。でもこれで問題はありません。なぜかというと「パラメタテキストにおいては12マクロパラメタは番号順に並んでいなければならない」という規則が存在するからです13

先の例をもう一度見てみましょう。

\catcode`\&=6 % パラメタ文字にする
\def\macroA#1;#2{#2-#1}
\def\macroB#1;&2{#2-#1}

先述の規則を適用すると、パラメタトークンでの置き換えの結果は以下のようになります。

  • \macroAのパラメタテキストは#₆1₁₂;₁₂#₆2₁₂#ₚ;₁₂#ₚ
  • \macroBのパラメタテキストは#₆1₁₂;₁₂&₆2₁₂#ₚ;₁₂&ₚ

この通り、#ₚ&ₚは異なるパラメタトークンであるため、2つのマクロのパラメタテキストは一致しないのでした。

前節で「暗黙のパラメタ文字トークンを置換テキストに使う」話をしましたが、容易に予想されるように、暗黙のパラメタ文字トークンはパラメタテキストでも使用できます。

\let\HASH=# % 暗黙のパラメタ文字トークン
\def\macroA#1;#2{#2-#1}
\def\macroB#1;\HASH2{#2-#1}

この場合に\macroA\macroBは等価になるでしょうか。これまでの解説を読んでいれば答えがわかるはずなので、一度自分でじっくりと考えてみてください:snowman:

scthinkingtime.png

正解は……。

\let\HASH=# % 暗黙のパラメタ文字トークン
%↓パラメタテキスト=“#ₚ;₁₂#ₚ”
\def\macroA#1;#2{#2-#1}
%↓パラメタテキスト=“#ₚ;₁₂#ₚ”
\def\macroB#1;\HASH2{#2-#1}
% 両者のパラメタテキストが一致するので等価になる。
\Check\macroA\macroB %==>SAME

この通り等価になります。パラメタテキスト用のパラメタトークンは文字コードの情報はもっていますが「元のパラメタ文字トークンが暗黙であったか」の情報をもつ余地はありません。明示と暗黙の#₆はともに#ₚに置き換えられて区別がつかないのでした。

おつかれさまでした:smiley:

まとめ

:blush:​「メリークリスマス!」​:gift::snowflake::christmas_tree::snowman:

  1. 「マクロの等価性」は専ら\ifxでの判定で使われる概念なので、TeX言語の参考書においては\ifxの解説のところで扱われることが多いはずです。

  2. 「パラメタトークン」の扱いについては後で詳しく述べます。それまでは取りあえず「#1はそのまま2トークンとして扱われる」と思ってかまいません。

  3. これらの接頭辞の機能が何かを説明することは本記事のスコープ外とします。

  4. 本記事では「マクロを定義する作用をもつ(\def\edef\gdef\xdefのプリミティブをもつ)文」のことを定義文と呼ぶことにします。

  5. \def\gdefの違い(および\edef\xdefの違い)についても同様です。

  6. そもそも制御綴のトークンは\csnameでも生成できるわけで、この場合には「制御綴の表記中のエスケープ文字」という概念は意味を成しません。

  7. マクロ定義文のパラメタテキストの末尾、つまり{₁(等のグループ開始文字トークン)の直前に#₆(等のパラメタ文字トークン)を置くと、そのマクロは「波括弧区切り」になります。この定義は「パラメタテキストと置換テキストの両方の末尾に当該のグループ開始文字トークンを追加する」という処理によって実現されます。

  8. パラメタトークンはマクロ(“マクロ型の値”)を構成するパラメタテキストや置換テキストのトークンの中にのみ存在するという意味で、極めて特殊な存在です。入力バッファのトークンの中には決して含まれないため、例えば「パラメタトークンと等価な制御綴」のようなものを作り出すことは原理的にできません。

  9. 添字のaは“argument”(引数)の略です。

  10. 制御綴やアクティブ文字に対して非アクティブ文字を\letしたもの。

  11. 添字のpは“parameter”(パラメタ)の略です。

  12. 先の例で\macroA\macroBの置換テキストが#2-#1であることからわかるように、「マクロパラメタは番号順に並ぶ」という性質は置換テキストにおいては成立しません。

  13. 例えばパラメタトークンで置き換えた後のパラメタテキストが_₈Aₚ_₈Bₚ_₈Cₚ_₈だったとすると、AₚBₚCₚがそれぞれ1番目・2番目・3番目のマクロパラメタであることは明らかです。つまり元のトークン列は_A₆1_B₆2_C₆3_だったわけです。

6
2
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
6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?