4
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?

More than 5 years have passed since last update.

徹底解説! \expandafter 活用術(ホンキ編)

Last updated at Posted at 2016-12-25

本記事は「徹底解説! \expandafter 活用術(キホン編)」の続きにあたります。「キホン編」では単発の \expandafter の使い方を学びましたが、この「ホンキ編」では \expandafter を自在に使いこなすのに不可欠な「\expandafter の鎖」について解説します。めざせ \expandafter マスター!

※「キホン編」で用いた表記や用語をこの記事でも踏襲します。

image-sudden-expandafter.png

\expandafter の“鎖則”

「キホン編」の最後で「展開可能プリミティブの一覧」を載せましたが、その中には他ならぬ \expandafter が含まれています。\expandafter が展開可能なのはある意味当然で、なぜなら最初に挙げた「\expandafter の定義」は「\expandafter プリミティブの展開の規則」そのものだからです。

\expandafter で \expandafter する件

それでは、\expandafter の定義の B の位置に \expandafter がある場合、つまり“\expandafter A \expandafter”を展開するとどうなるか、考えてみましょう。\expandafter がある場合、その先に最低2つのトークンがあるはずですので、次の形を考えます。

\expandafter A1 \expandafter A2 B
ただし B… ⇒ B'… とする

「定義」の中の“B…”に相当する部分がここでは \expandafter A2 B… なので、いつもの図式で考えると以下のようになるでしょう。

\expandafter A1 \expandafter A2 B
  [\expandafter A2 B… ⇒ ???]
A1 ???

??? の部分は何でしょう。これは \expandafter の単純な展開です。

\expandafter A2 B
  [B… ⇒ B'…]
A2 B'

この結果をそのまま当てはめると、以下のようになります。

\expandafter A1 \expandafter A2 B
  [\expandafter A2 B
    [B… ⇒ B'…]
  ⇒ A2 B'…]
A1 A2 B'

つまり、A1A2 は変化せずに B が展開される、という結果になりました。

もっと \expandafter を \expandafter してみる

では、ここで B がまた \expandafter だったらどうなるでしょうか。先と同様に考えると、結果は以下のようになります。

\expandafter A1 \expandafter A2 \expandafter A3 B
  [\expandafter A2 \expandafter A3 B
    [\expandafter A3 B
      [B… ⇒ B'…]
    ⇒ A3 B'…]
  ⇒ A2 A3 B'…]
A1 A2 A3 B'

最終的な結果だけ見ると、以下の式が成り立ちます。

  • \expandafter A1 \expandafter A2 B… ⇒ A1 A2 B'
  • \expandafter A1 \expandafter A2 \expandafter A3 B… ⇒ A1 A2 A3 B'

素敵な表記のおやくそく

さて、もっと先に進みたいわけですが、そうすると、ただでも長い \expandafter という制御綴が大量に並ぶことになって、煩わしいことこの上ありません。そこで次のような、チョット素敵な表記規則を設けましょう。

  • \expandafter の略記として :sushi: と書く。

この表記法を取り入れると、先の展開規則は次のように書けます。

  • :sushi: A1 :sushi: A2 B… ⇒ A1 A2 B'
  • :sushi: A1 :sushi: A2 :sushi: A3 B… ⇒ A1 A2 A3 B'

チョット素敵になりましたね!1

“\expandfater の鎖”の法則

本題に戻りましょう。先ほど行った、「B\expandafter Ai B に置き換えて、全体の展開を考える」という操作は何度でも繰り返すことができます。この方法で \expandafter を増やすことで次のような一連の規則を導き出せます。(興味がある人は実際に導出してみてください。)

  • :sushi: A1 :sushi: A2 B… ⇒ A1 A2 B'
  • :sushi: A1 :sushi: A2 :sushi: A3 B… ⇒ A1 A2 A3 B'
  • :sushi: A1 :sushi: A2 :sushi: A3 :sushi: A4 B… ⇒ A1 A2 A3 A4 B'
  • :sushi: A1 :sushi: A2 :sushi: A3 :sushi: A4 :sushi: A5 B… ⇒ A1 A2 A3 A4 A5 B'

これを一般化して2得られるのが、次に示す「\expandafter の鎖則」です。

B… ⇒ B'
であるとき
:sushi: A1 :sushi: A2 …… :sushi: An B… ⇒ A1 A2 …… An B'

\expandafter が連なってできた鎖がトークン列に“絡まっている”」ように見えるため「鎖則(chain law)」の名前がついています。

単発の \expandafter を使うと「先頭にある A」が展開される前に「その次にある B」を展開することができたのですが、「\expandafter の鎖」を使うと「もっと後ろにある B」を先に展開することができるわけです。

\expandafter の鎖則のガイドライン

\expandafter のガイドライン」にならって、「どういう場合に \expandafter の鎖を使うべきか」という観点で整理した「\expandafter の鎖則のガイドライン」を用意しました。

  • AB… のようなトークン列があり、このままでは A が実行される。
  • しかし、A の実行の前に Bを展開したい。
  • その場合、A… の部分にある全てのトークンの直前に \expandafter を置けばよい。

例題:マクロの内容に追加する話

【例題4】引数無しのマクロ(内容は可変)\xParHook が定義されている。

% 例えば
\def\xParHook{\scsnowman[muffler=red]\relax}

このとき、「\xParHook の内容の末尾に \xMyHook というトークンを追加する」コードを書け。

(上記の例の場合、\xParHook の一回展開が \scsnowman[muffler=red]\relax\xMyHook となるべき。)

まずは「\xParHook の後に \xMyHook を追加したもので \xParHook を再定義する」と考えて次のような“原型”を作ってみます。

未完成
% あくまでも原型
\def\xParHook{\xParHook\xMyHook}

もちろんこのままでは \xParHook が無限ループになってしまいます。ここで必要なのは「\def が実行される前に(後ろの)\xParHook を展開する」ことです。この状況を「ガイドライン」を当てはめてみましょう。

  • \def\xParHook… というトークン列があり、このままでは \def が実行される。
  • しかし、\def の実行の前に \xParHook を展開したい。
  • その場合、\def… の部分にある全てのトークンの直前に \expandafter を置けばよい。

今の場合、「\def… の部分」のトークン列とは「\def``\xParHook``\{」です。従って、この部分に \expandafter の鎖を絡ませればよいわけです。つまり以下のようになります。

例題4の解答
\expandafter\def\expandafter\xParHook\expandafter{%
  \xParHook\xMyHook}

例題:キレイキレイにする話

【例題5】制御綴の列を内容にもつマクロ \xGarbageList がある。

% "消したい"制御綴を入れておく
\def\xGarbageList{\rm\sf\tt\bf\it\sl\sc}

このとき、LaTeXの内部命令 \@tfor を利用して「\xGarbageList の内容に含まれる各々の制御綴の定義を消去する(未定義に戻す)」コードを書け。

先ほどと同様に、ますは原型となるコードを作って、それから \expandafter を加えていきます。

未完成
\@tfor\x:=\xGarbageList\do{%
  \let\x\@undefined}% \@undefined は未定義の制御綴

まずループの中ですが、このままでは \x 自体に代入されてしまいます。「let の実行の前に \x を展開したい」ので \let の前に \expandafter を置きます。

  \expandafter\let\x\@undefined

次に \xGarbaseList の展開についてですが、「\@tfor の実行の前に \xGarbaseList を展開したい」ということなので、ガイドラインに従うと、「\@tfor``\x``:``=」の部分に \expandafter の鎖を絡ませればよいわけです。

例題5の解答
\expandafter\@tfor\expandafter\x\expandafter:\expandafter=\xGarbageList\do{%
  \expandafter\let\x\@undefined}

\expandafter の鎖に潜む罠

\expandafter の鎖」は応用範囲がとても広くて便利なのですが、重大な欠点があります。それは「コード中の鎖が“絡んでいる”部分の可読性が著しく損なわれる」ということです。例えば、先ほどの例題の解答のコードを改めて見返してください。「\@tfor\x:=...\do」というループ構造を示す外見が、ほとんど視認できなくなってしまっています。後で“絡んでいる”部分のコードを改修しようとしても、その作業は困難を極めることになるでしょう。

この場合、鎖が“絡んでいる”部分の「\@tfor\x:=」を一度マクロにすると、単発の \expandafter で済ませられます。

例題5の解答
\def\xTforXIn{\@tfor\x:=}
\expandafter\xTforXIn\xGarbageList\do{%
  \expandafter\let\x\@undefined}

まだまだ解り難さは残っていますが、少なくとも実際に実行されるコードが「見えて」いるため、将来の改修に対応することができるでしょう。

\expandafter の長連の規準

このように、「\expandafter の長い鎖」の使用は厳に慎まれるべきです。私自身は以下のような「\expandafter の長連の規準」を推奨しているので参考にしてください。

  • \expandafter の3重を超える鎖が発生した場合は、それを回避する策をホンキで考えよう。
  • \expandafter の5重を超える鎖は絶対に絶対に絶対に回避しよう。

この規準はかなり厳しいのは確かですが、実際にこのくらいに「長連の回避」を考える機会が得られないと、「回避するコツ」がなかなか身に付かないものです。

\expandafter の“ベキ乗則”

これまでの話では、\expandafter を(単発でも鎖でも)使うと、後ろにあるトークンが一回展開できる、ということでした。それでは、後ろにあるトークンを「何回も」展開したい場合はどうすればいいでしょうか。考えましょう。

後ろを2回展開したい話

\def\csB{\csBi}
\def\csBi{\csBii}
\def\csBii{\csBiii}
\def\csBiii{\csBiv}
% つまり \csB ⇒ \csBi ⇒ \csBii ⇒ \csBiii ⇒ \csBiv

例えば、こういう定義があったとして、

\csA``\csB」の後ろにある \csB2回展開したい
(つまり「\csA``\csBii」に変えたい)

という状況を考えます。どのように \expandafter を置けばよいでしょうか。

\expandafter では1回しか展開できない」という原則は変えられないので、これを実現するには「展開自体を2回にする」必要があることは確かです。

【概念図】

\csA``\csB  …⓪
\csA``\csBi  …①
\csA``\csBii  …②

まずは「①⇒②になるように①に \expandafter を加える」という問題を考えましょう。\csBi の一回展開が \csBii なので、これは単純な単発の \expandafter で解決できます。

【①⇒②は完成】

:sushi: \csA``\csB  …⓪
:sushi: \csA``\csBi  …①
  [\csBi\csBii]
\csA``\csBii  …②

ここで⓪も①と同じ形になるように前に \expandafter を置きました。この状態で「⓪⇒①になるように⓪に \expandafter を加える」という問題を考えます。よく見ると、これは「\expandafter の鎖のガイドライン」が当てはめられることに気づきます。

  • :sushi: \csA``\csB というトークン列があり、このままでは :sushi: が実行(展開)される。
  • しかし、:sushi: の実行の前に \csB(⇒ \csBi)を展開したい。
  • その場合、:sushi: \csA の部分にある全てのトークンの直前に :sushi: を置けばよい。
    • つまり、:sushi::sushi: :sushi:\csA:sushi: \csA と置き換える。
    • 従って結果は :sushi: :sushi: :sushi: \csA となる。

つまり、完成形は以下のようになります。結果的に、先頭に3個:sushi: を置けばよいことになります。

【2回展開の完成形】

:sushi: :sushi: :sushi: \csA``csB  …⓪
  [\csB\csBi](鎖則)
:sushi: \csA``\csBi  …①
  [\csBi\csBii](単発)
\csA``\csBii  …②

後ろを3回展開したい話

2回展開ができたので、次は3回展開を考えてみます。

\csA``\csB」の後ろにある \csB3回展開したい
(つまり「\csA``\csBiii」に変えたい)

3回展開なので、全体のトークン列も3回展開する必要があります。先ほどと同様に「後ろから順に」考えてみましょう。

【概念図】

\csA``\csB  …⓪
\csA``\csBi  …①
\csA``\csBii  …②
\csA``\csBiii  …③

②⇒③に単発の \expandafter を適用します。

【②⇒③は完成】

:sushi: \csA``\csB  …⓪
:sushi: \csA``\csBi  …①
:sushi: \csA``\csBii  …②
  [\csBii\csBiii](単発)
\csA``\csBiii  …③

①⇒②に \expandafter の鎖を適用します。

【①⇒②⇒③は完成】

:sushi: :sushi: :sushi: \csA``\csB  …⓪
:sushi: :sushi: :sushi: \csA``\csBi  …①
  [\csBi\csBii](鎖則)
:sushi: \csA``\csBii  …②
  [\csBii\csBiii](単発)
\csA``\csBiii  …③

最後に⓪⇒①の部分ですが、これも鎖則が適用できる形になっていることが判るでしょう。つまり、:sushi: :sushi: :sushi: \csA\expandafter の鎖(もう「:sushi:の鎖」でいいよね)を絡ませればよいわけです。

ここで少し一般的に考えてみます。「:sushi:n個並んだ後にトークン X がある」というトークン列に対して「:sushi: の鎖の絡ませる」(列に含まれる各々のトークンの前に :sushi: を置く)とどうなるでしょうか。「:sushi:n個」の部分は個数が倍に増えて「:sushi: が2n個」になり、さらに X:sushi: X に変わるので、結局 X の前に2n+1個の :sushi: がある恰好になります。この結果を「\expandafter 倍増の規則」(いや「:sushi: 倍増の規則」かな?)と呼ぶことにしましょう。

:sushi:×n X
:sushi: の鎖を絡ませると
:sushi:×(2n+1) X
になる3

この規則に従うと、:sushi:×3 \csA に鎖を絡ませた結果は :sushi:×7 \csA となります。従って、結果的に、先頭に7個:sushi: を置けばよいことになります。

【3回展開の完成図】

:sushi: :sushi: :sushi: :sushi: :sushi: :sushi: :sushi: \csA``\csB  …⓪
  [\csB\csBi](鎖則)
:sushi: :sushi: :sushi: \csA``\csBi  …①
  [\csBi\csBii](鎖則)
:sushi: \csA``\csBii  …②
  [\csBii\csBiii](単発)
\csA``\csBiii  …③

さらに一歩進めて、4回展開はどうなるでしょうか。これまでの手順と同様に考えると、結局 :sushi:×7 \csA の部分に再度 :sushi: の鎖を絡ませれば済むことが判るでしょう。そして「:sushi: 倍増の規則」により、:sushi: の個数は 2×7+1 で15個に増えます。

【4回展開の場合】

:sushi: :sushi: :sushi: :sushi: :sushi: :sushi: :sushi: :sushi: :sushi: :sushi: :sushi: :sushi: :sushi: :sushi: :sushi: \csA``\csB
:sushi: :sushi: :sushi: :sushi: :sushi: :sushi: :sushi: \csA``\csBi
:sushi: :sushi: :sushi: \csA``\csBii
:sushi: \csA``\csBiii
\csA``\csBiv

ベキ乗の法則

ここで、今までの考察の結果を、一般的なトークン列に対する規則としてまとめてみましょう。

【1回展開】(\expandafter の定義)

B… ⇒ B'
であるとき
:sushi: A B… ⇒ A B'

【2回展開】

B…  ⇒×2  B'
であるとき4
:sushi:×3 A B…  ⇒×2  A B'

【3回展開】

B…  ⇒×3  B'
であるとき
:sushi:×7 A B…  ⇒×3  A B'

【4回展開】

B…  ⇒×4  B'
であるとき
:sushi:×15 A B…  ⇒×4  A B'

これを見ると、先頭に置くべき :sushi: の個数は

1 → 3 → 7 → 15 → …

のように増えています5。この数列の第n項の値は 2n−1 で求められます。ここから「n回展開」に関する一般的規則を導き出すことができます。

n回展開】

B…  ⇒×n  B'
であるとき
:sushi:×(2n−1) A B…  ⇒×n  A B'

\expandafter の個数が2のベキに従って増えていく様子から、この規則は「\expandafter のベキ乗則(power law)」と呼ばれることがあります。

ベキ乗則よりも大事なこと

この“ベキ乗則”は理論的には非常に面白い結果なのですが、しかし私はベキ乗則は覚える必要はないと考えています。なぜかというと、ベキ乗則を単純に適用できる状況は実用上はそう多くはないと考えているからです。実際のTeX言語のプログラミングで「複数回展開する」状況はもっと多様です。

  • 複数回展開したいトークンがずっと後ろにある(つまり鎖則と複合する場合)
  • そもそも先に展開したいトークンが複数個ある
    • しかも各々のトークンで必要な展開回数が異なる

従って、「複数回展開する」状況に対応できるようになるために必要なのは、ベキ乗則を定理として覚えることではなく「それを導出する方法」を習得することだといえます。具体的には、以下のような要素を身につけることが必要です。

  • 鎖則を自由に使いこなす
  • 展開過程を「後ろから順に」構築する
  • \expandafter 倍増の規則」

そして、展開制御を上手に行う上で大事なコツは

そもそも**「後ろのトークンを何度も展開する状況」を作らない**

ということです。ベキ乗則から判るように、後ろのトークンを複数回展開しようと試みると、\expandafter が文字通り指数爆発してしまいます。結果的に、それほど複雑でない状況であってもコードが「\expandafter まみれ」になって全く読めなくなる、という悲惨な状況が簡単に発生します。

\expandafter の高度な活用法を学習する際には、ぜひとも、他の展開制御の手法(\edef による完全展開、など)も一緒に習得して、「\expandafter が爆発しないように上手く展開制御する」ことを心がけましょう。

めざせ展開制御マスター!

練習問題(ホンキ編)

※ここの練習問題において、カテゴリコードの設定はLaTeXの \makeatletter の状態を仮定します。また、my@ で始まる名前の制御綴(例えば \my@val)は未定義であり自由に使ってよいものとします。

問題6: \expandafter の群れを展開する話

次のようなマクロが定義されているとする。

\def\gobble#1{}
\def\twice#1{#1#1}

この時、次のトークン列を一回展開した結果はどうなるか。

\expandafter\expandafter\expandafter\twice\expandafter
    \twice\expandafter{\gobble\expandafter}{\gobble}

問題7: 名前で \let する話

etoolbox パッケージでは、\let について「制御綴の代わりにその名前を指定する」変種として以下の命令を提供している。

  • \cslet{<名前A>}\制御綴B\制御綴B を「名前が <名前A> の制御綴」にコピーする。
  • \letcs\制御綴A{<名前B>} : 「名前が <名前B>の制御綴」を\制御綴A` にコピーする。
  • \csletcs{<名前A>}{<名前B>} : 「名前が <名前B>` の制御綴」を「名前が `<名前A>` の制御綴」にコピーする。

これらの命令を自分で実装せよ。ただし制御綴の名前の引数は「完全展開すると文字トークンの列になる」ことを仮定してよい。

% 以下の4つの文は全て等価になるべき
\let\foo\bar
\cslet{foo}\bar
\letcs\foo{bar}
\csletcs{foo}{bar}

問題8: マクロの前後にナニカを追加する話

次の機能をもつマクロ \enclose を実装せよ。

  • \enclose\制御綴A{<トークン列1>}{<トークン列2>} : 引数無しのマクロ \制御綴A について、その内容を、「前に <トークン列1>、後ろに <トークン列2> を追加したトークン列」に置き換える。
    • \制御綴A は引数無しのマクロであると仮定してよい。

以下に \enclose の使用例を示す。

\def\xTest{\ARE\LaTeX}
\enclose\xTest{\ARE\TeX}{\ARE{TikZ}}
% \xTest ⇒ \ARE\TeX\ARE\LaTeX\ARE{TikZ}

まとめ

皆さんも \expandafter で幸せになりましょう!

  1. なんだ、結局奇をてらっているじゃん……。

  2. 一般の自然数nについての言明の証明には数学的帰納法を利用します。

  3. :sushi:n個並んだトークン列」を「:sushi:×n」と表記します。

  4. Xn回展開すると Y になる」という言明を「X ⇒×n Y」と表記します。

  5. 先ほどの「:sushi: 倍増の規則」により、この数列は漸化式 an = 2an−1 + 1 に従うことになります。

4
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
4
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?