17
14

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-12-24

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

image-sudden-expandafter.png

\expandafter is 何

\expandafter はTeX言語のプリミティブの一種ですが、TeX言語のコードを書くときに限らず、(La)TeX者の日常生活における様々な場面で利用されています。

  1. お酒のネタにする(
  2. (特にTeX関連の)会議におけるノベルティグッズの意匠にする(
  3. 絵を描く際のモチーフにする(例1例2
  4. 独自の健康法のモチーフにする(
  5. 独自の体操のモチーフにする(例1例2
  6. とにかく連呼する(例1例2
  7. TeX言語のプログラムにおいて、展開制御のために書く

このうち、最初の6つに関しては、特に難しく考えることは何もありません。単に本能の赴くがままに \expandafter の名を叫べばよいわけですから1

これに対して、最後の「展開制御のために書く」についてはそう簡単にはいきません。TeX言語の学習が進んである程度複雑なプログラムを書く段階に至ると、「展開の順番を変えたい」と思って \expandafter を試す人は多いようです。しかし、この「展開制御」というのはTeX言語特有の概念であるため、数多くのプログラム言語を制覇した強者であっても、マトモな理解を得るのはなかなか容易ではありません2

世の中には \expandafter の挙動を理解するための“チョット変わったアプローチ”を提唱している人もいます。しかし、この記事では特に奇をてらうことなく、\expandafter素朴な定義にホンキで取り組むことでその理解を目指します。

前書きの最後に、念のため、コレを書いておきましょう。

:sushi: TeX言語注意!

Before \expandafter

そもそも「展開制御」が何かの役に立つためには「展開」についての理解が不可欠です。すなわち、TeX言語の「展開」の仕様、またその前提となる「字句解析(トークン化)」の仕様について十分に理解していない間は、\expamdafter などの「展開制御」に手を出すべきでないのは当然でしょう。

そこで、まずは「\expandafter 以前」の理解確認のため、簡単なクイズをやってみましょう。

【クイズ1】次のTeXのソースを字句解析すると何個のトークン3からなると見なされるか。ただし、カテゴリコードの設定はLaTeXの通常通りであると仮定する。

  \someproc A  {B C\relax  } {`\\}\nil %?

【クイズ2】次のコードが予め実行されていたとする。

  \def\foo#1{foo}

この場合、次のトークン列を(先頭で)一回展開した結果のトークン列はどうなるか。

  \csname\foo\foo\endcsname\foo\foo\relax

自信をもって答えを出せたならば合格です4\expandafter をホンキで理解したい人にとっては楽勝でしたね!

表記のおやくそく

展開制御は、字句解析された結果のトークン列に対して行われるものです5。そのため、トークン列を曖昧さなく示すために、本記事では次のような「トークン列の記法」を使うことにします。

  • 制御綴6のトークンを \csname、文字トークンを A のように等幅フォントで書きます。特に空白文字トークンを と書きます。
  • 見やすさのため、トークン列は abc のように間を空けて書きます。間に空白文字トークンがあるわけではないことに注意してください。空白文字トークンは常に で表されます。
  • トークンを表す変数として AB、… などの英大文字を使います。さらに、トークン列を A… のように表すことにします。
  • 「トークン列 A… を一回展開すると B… になる」という言明を「A… ⇒ B…」と表記します。

例えば、“\someproc A {B C\relax } {`\\}\nil %?”(先の問題に出てきたもの)を字句解析した結果のトークン列は次のようになります。

\someprocA{BC\relax}{​`​\\}\nil

※本記事では一貫して「LaTeX上のTeX言語プログラミング」を取り扱います。また、特に断りがない限り、「LaTeXの \makeatletter の状態」のカテゴリコード設定を仮定します。

\expandafter のキホン

\expandafter、コワクナイヨー

一般に難解といわれる \expandafter ですが、実はその定義は、次の示す通り、いたって単純なものです。

B… ⇒ B'
であるとき
\expandafterA B… ⇒ A B'

これだけです。しかし、単純だからこそ、その確実な理解が大事なわけです。

\expandafter はなぜ動くのか

簡単な例で調べてみましょう。

\def\csAA{\csA\csA}
\def\csBB{\csB\csB}

このようにマクロが定義されているとします。この時、トークン列 \csAA\csBB を一回展開するとどうなるでしょうか?

\csAA\csBB
\csA\csA\csBB

これは当たり前ですね。ではこの先頭に \expandafter を付けたものを考えましょう。これの一回展開はどうなるでしょうか?

\expandafter\csAA\csBB

定義に戻って考えましょう。今の場合、A に相当するのが \csAAB… に相当するのが \csBB です。\expandafter の定義に従うと、その展開結果を知るためには、B… の一回展開の結果である B'… を知る必要があります。

\csBB (←これが B…)
\csB\csB (←これが B'…)

\expandafter の展開結果は A B'… ですから、結局 \csAA\csB\csB となります。以上の結果を改めて整理してみましょう。

\expandafter\csAA\csBB
  [\csBB\csB\csB]
\csAA\csB\csB

結局 \expandafter の追加により、A が展開される前に B が展開されたことになります。

\expandafter のガイドライン

以上の結果を「どういう場合に \expandafter を使うべきか」という観点で改めて整理してみます。

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

以下ではこれを「\expandafter のガイドライン」と呼ぶことにします。

例題:はじめての \expandafter

定義が解ったところで、さっそく \expandafter を利用したコードを書いてみましょう。

【例題1】TeXのプログラム中に、以下のような \let 文があった。

  % \xName に結果(\xResult)を格納する
  \let\xName\xResult

ところが、プログラムの改修で、letのコピー先の制御綴(\xName)を可変にする必要が生じた。このため、コピー先の制御綴を指定するためのマクロ \xTargetCs を用意した。

  % 結果を格納する変数(マクロ名)
  \def\xTargetCs{\xName}%

このとき、「\xTargetCs の内容7の制御綴(上例の場合は \xName)に \xResult をコピー(\let)する」というコードを書け。

もちろん以下のコードは正しくありません。

失敗
\let\xTargetCs\xResult % ダメ

これでは(\xTargetCs の内容である)\xName ではなく \xTargetCs そのものが書き換えられてしまいます。この状況を先程の「\expandafter のガイドライン」と照合してみましょう。

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

ピタリと当てはまりました。つまり、次のようにすればよいわけです。

例題1の解答
\expandafter\let\xTargetCs\xResult

期待通りに動くかどうか、シミュレートしてみましょう。

\expandafter\let\xTargetCs\xResult
  [\xTargetCs\xName]
\let\xName\xResult

大丈夫ですね。

例題:引数を完全展開したい話

\expandafter の最もキホン的な使い方を心得たので、もう少し応用してみましょう。

【例題2】次のように、\includegraphics に渡すべき引数がマクロとして与えられている。

  \def\xImageOpt{\xImageSysOpt,\xImageUserOpt}% \includegraphics のオプション
  \def\xImageSysOpt{width=.8\linewidth}
  \def\xImageUserOpt{pagebox=artbox}
  \def\xImageFile{image-1.pdf}% \includegraphics の対象のファイル名

次のような形の \includegraphics 文を実行したいが、引数がマクロのままでは正しく処理されない8

失敗
  % 引数のマクロを展開しないとダメ
  \includegraphics[\xImageOpt]{\xImageFile}

「引数(だけ)を完全展開してから \includegraphics を実行する」ようにするコードを書け。

引数の部分“[\xImageOpt]{\xImageFile}”を完全展開したいわけなので、まずそこに \edef を適用することを考えます。

※トークン列の完全展開(full expansion)を得るには \edef が必要です。\expandafter では対処できません。

% 引数の部分を完全展開する
\edef\xArgs{[\xImageOpt]{\xImageFile}}
% これで \xArgs ⇒ [width=.8\linewidth,pagebox=artbox]{image-1.pdf} となる

この \xArgs\includegraphics 文のしかるべき位置に置きます。

未完成
% \xArgs は"引数"である
\includegraphics\xArgs

ここで再び「\expandafter のガイドライン」を思い起こしましょう。

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

というわけで答えは次のようになります。(最初の \edef から書いておきます。)

例題2の解答
\edef\xArgs{[\xImageOpt]{\xImageFile}}
\expandafter\includegraphics\xArgs

この「引数を \edef する → \expandafter」のコンボは応用範囲が広いので、ぜひ身につけておきましょう。

補足:\expandafter しない方法

先ほどの例題2ですが、次のように \noexpand を使っても解決できます。

例題2の別解
\edef\xNext{\noexpand\includegraphics[\xImageOpt]{\xImageFile}}
\xNext

これは「次に実行すべき文の正しいトークン列を \edef と展開抑止を組み合わせて作る」というパターンです。これも応用が効くので覚えておくといいでしょう。

チョット注意

トークンとはトークンである

ここでまたクイズです。次のようなコードを考えます。

\everypar\expandafter{\the\everypar\xSomething}\xAnother

これを実行すると、\everypar の実行9の後に \expandafter が展開されますが、この時に(定義における)A に相当する部分は何でしょうか?

\expandafter の直後にあるトークンが A です。なので、この場合は {(開き波括弧)となります。これが正解です。

特に難しい話ではないはずですが、ここでTeX言語初心者がよくやる間違いは、“A の部分”を「{\the\everypar\xSomething}」だと考えてしまうことです。そもそも A単一のトークンを表す変数であり、それが複数のトークンからなる列 {\the\everypar\xSomething} を表すことは絶対にありえません。TeX言語の文法では {...} で囲まれた「グループ」を一体のものとして扱う場面がよくありますが、この「グループ」自体は決してトークンではないことに注意してください。

ちなみに、今の場合の \expandafter の展開過程は以下のようになります。ただしトークン列パラメタ \everypar の現在の値を“\xFooBar”とします10

\everypar\expandafter{\the\everypar\xSomething}\xAnother
  [\the\everypar\xFooBar]
⇒〈\everypar{\xFooBar\xSomething}\xAnother

展開不能トークンで \expandafter する件

\expandafterA B… の展開の際には B が展開されるわけですが、この時もし B が展開不能なトークンだったらどうなるでしょうか?

% この場合Bは'{'であり展開不能
\expandafter\begin{array}\xArgs

この場合は、便宜的に「BB 自身に展開される」と解釈されます。

\expandafter\begin{array}\xArgs
  [{{](と見なす)
\begin{array}\xArgs

つまり、B が展開不能な場合は \expandafter全くの無駄、ということになります。

それでは、A が展開不能な場合の \expandafter はどうでしょうか? 一見すると、こちらも同様に無駄であるようにも見えますが、実は違います。現に、最初の例題の正解のコードにおいて、A に相当するのは展開不能なプリミティブである \let ですが、それでも \expandafter は有意義でした。

% Aは'\let'であり展開不能
\expandafter\let\xTargetCs\xResult

この \expandafter がなぜ意味をもつかというと、それは「一旦 \let が実行されると、let文が完成するまで展開が抑止される」という性質があるからです。

\expandafterがない場合11

\let\xTargetCs\xResult
→〈\let\xTargetCs\xResult  (let文開始)
→〈\let\xTargetCs\xResult  (展開されない)

\expandafterがある場合]

\expandafter\let\xTargetCs\xResult
  [xTargetCs\xName](展開される)
\let\xName\xResult
→〈\let\xName\xResult  (let文開始)
→〈\let\xName\xResult

このように、構文上で展開抑止が起こる箇所では「A が展開不能」であっても \expandafter は必ずしも無駄にならないのです12

プリミティブで \expandafter する件

「展開不能なトークン」に関してよくある誤解は「プリミティブは展開不能である」というものです。確かにプリミティブの多く(\let 等)は展開不能ですが、実際には展開可能なプリミティブ\the 等)もあります。例えば先ほどの \everypar の例では \the\everypar\xFooBar という展開を扱いました。

参考として、TeX言語の展開可能なプリミティブのうち重要なものを列挙しておきます。

  • 制御綴構成: \csname
  • 値取得・文字列化: \the \string \meaning \number \romannumeral
  • 条件分岐: 各種ifトークン(\ifnum 等) \else \fi
  • 展開制御: \noexpand \expandafter

これらは展開可能であるため、\expandafter の定義における B の位置に来る場合があります。

例題:\csname で \expandafter する件

これらの展開可能プリミティブのうち、\expandafter と絡んでよく使われるのが \csname です。なので、これについて詳しく考えましょう。

【例題3】例題1では、コピー先の制御綴を可変にするために、その制御綴そのものを入れたマクロ \xTargetCs を用意した。

  \def\xTargetCs{\xName}

これを少し変えて、次のように“制御綴の名前13”を内容に持たせる仕様にしたい。

  \def\xTargetCsName{xName}% コピー先の制御綴の*名前*

では「\xTargetCsName の内容の文字列を名前とする制御綴に \xResult をコピー(\let)する」というコードを書け。

\xTargetCsName(の内容)の名前をもつ制御綴を作るために \csname\xTargetCsName\endcsname としましょう。ここで問題なのは「これの一回展開はどうなるか」ということです。一回展開しただけで目的の制御綴(\xName)になるのでしょうか?

答えはYesです。つまり \csname\endcsname の一回展開は“~”の部分の名前をもつ単一の制御綴となります14。この際に“~”の部分は完全展開されるという規則になっています。

\csname\xTargetCsName\endcsname
  [\xTargetCsName ―(完全展開)→ xName]
\xName

一回展開で十分なことが判れば、あとは簡単ですね。\let の実行より先に \csname を一回展開すればよいので以下のようになります。

例題3の解答
\expandafter\let\csname\xTargetCsName\endcsname\xResult

\expandafter\let\csname\xTargetCsName\endcsname\xResult
  [\csname\xTargetCsName\endcsname\xName]
\let\xName\xResult

練習問題(キホン編)

以上で、\expandafter の最も基本的な使い方(単発の \expandafter)についての解説は終わりです。ここまでの内容をちゃんと理解できたかを確認するため、キホン的な練習問題に挑戦してみましょう。

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

問題1: \expandafter を展開する話

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

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

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

\expandafter\gobble\twice\gobble\twice\twice\gobble

問題2: \@​namedef を自作する話

LaTeXには「制御綴の代わりに制御綴の名前を指定してマクロを定義する」ための \@namedef という内部マクロが存在する。例えば、次の2つの文は等価な動作を行う。

% \@namedef{<名前>}<パラメタテキスト>{<置換テキスト>}
\@namedef{Hoge}#1#2{\message{#1 and #2}}

% ↑は↓と同等な動作をする
\def\Hoge#1#2{\message{#1 and #2}}

では、この機能をもつ \@namedef を自分で実装せよ。

問題3: ドライブレターの有無を判定する話

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

  • \hasdrivespec{<文字列>} : 引数のトークン列を完全展開して得られる文字列について、その2文字目が : であるか(つまりWindowsのドライブレター付きの絶対パス名であるか)否かを判定し、その結果をスイッチ15 \if@tempswa に返す。
    • スイッチ \if@tempswa はLaTeXでは予め定義されている。
    • 引数は「完全展開すると“普通の文字列”(カテゴリコード11か12の文字トークンの列)になる」ことを仮定してよい。

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

\def\xOneDir{C:/tmp/tex}
\def\xOneFile{advent.txt}
\def\xOnePath{\xOneDir/\xOneFile}

\hasdrivespec{D:/fonts}\if@tempswa Yes\else No\fi
%→ "Yes"と出力
\hasdrivespec{\xOneFile}\if@tempswa Yes\else No\fi
%→ "No"と出力
\hasdrivespec{\xOnePath}\if@tempswa Yes\else No\fi
%→ "Yes"と出力

問題4: 制御綴を得る話

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

  • \makecs\制御綴A{<名前>}\制御綴A を、内容が「名前が <名前> である制御綴」であるマクロとして定義する。
    • 引数は「完全展開すると文字トークンの列になる」ことを仮定してよい。

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

\makecs\xTest{space}
% \xTest ⇒ \space となる
\def\&{and}
\makecs\xTest{exp\&after}
% \xTest ⇒ \expandafter となる

問題5: 一回展開を調べる話

e-TeX拡張をもつTeXエンジン16\showtokens というプリミティブを持つ。\showtokens{<トークン列>} を実行すると、\show と同様の情報表示の様式で、引数のトークン列が(展開されずに)端末にそのまま表示される。

latexの対話モード
*\showtokens{\foo\bar\expandafter}    %←'*'はプロンプト
> \foo \bar \expandafter .            %←引数がそのまま出る
<*> \showtokens{\foo\bar\expandafter}

?                                     %←入力待ちになる

では、この \showtokens プリミティブを用いて「与えられたトークン列の一回展開がどうなるか」を調べる手順を構成せよ。そしてその手順を利用して問題1に対する自分の解答が正しいことを確認せよ。

まとめ

\expandafrer は単なるTeXのプリミティブです。コワくありません!

「\expandafter のガイドライン」を活用して思う存分 \expandafter しましょう!

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

\expandafter のホンキが見たい人はこちらへ。


  1. \expandafter の読み方について特に決まりごとはありません。「エクスパンドアフター」でも「エキスパンドアフター」でも「エークスペァァァーンデァーフトゥゥ!!!!!」でも好きなように叫んでください。 

  2. 下手に自分の知っているプログラミング言語の概念で“近似”して先に急いで進もうとすると、本来の定義とのズレのために後々になって頭を抱えることになりかねません。 

  3. 本記事では、TeXの用語の“token”の訳語に「トークン」を用いることにします。 

  4. チョット気になる人のために正解を書いておきます。【クイズ1】15個です。【クイズ2】「\foo\foo\foo\relax」となります。 

  5. もちろん、実際のTeX処理系においては字句解析と展開は同時進行で処理されます(参考記事)。それでも、展開に関する議論を行う場合は、“字句解析に関するヤヤコシイ話”が絡むのを避けるために、敢えて“字句解析が済んだ後のトークン列”を前提にする方が適切でしょう。(特に、展開だけが行われている状況では、字句解析に影響を与える“カテゴリコードの変更”が起こらないことに注意しましょう。) 

  6. 本記事では、TeXの用語の“control sequence”の訳語を「制御綴」とします。他文献では「コントロール・シーケンス」と呼ばれることもあります。 

  7. この記事では、引数無しのマクロについて、一回展開した結果のトークン列のことを便宜的に「内容」と呼ぶことにします。 

  8. 一般的に、key-value形式の引数について、“key=value”全体がマクロになっているとそれは正常にパーズされません。 

  9. \everypar トークンの実行により、「\everypar パラメタに対する代入文」が開始されます。 

  10. 先頭にあった \everypar\expandafter の展開時には既に“実行されてしまっている”ため入力バッファ上にはありません。(このため図では〈 〉に入れて示しました。)この後 } まで読んだところで、“\everypar の代入文”として「\everypar{\xFooBar\xSomething}」が完成することになります。 

  11. ここの図の中の“→”は(“⇒”とは異なり)「実行の1ステップ」を表します。また〈 〉は「既に実行されてバッファ上にないトークン」を表します。「実行」とは何か、についてはあまり深く考えずに直感的に把握しましょう。(えっ) 

  12. もう一つ例を挙げます。先の「\everypar の代入文」のケース(everypar\expandafter{...})では、トークン列パラメタの代入文の文法規則として、{ を実行してから } を読むまでの間に展開抑止が起こります。従って、(展開不能な){ の前の \expandafter が意味をもつわけです。 

  13. TeX言語において、制御綴 \foo名前とは、先頭のエスケープ文字(\)を除いた“foo”のことを指します。ちなみに、LaTeXにおいて「命令の名前」という場合はエスケープ文字を含めた“\foo”を指すのが一般的です。 

  14. 結果の制御綴はそれ以上展開されません。つまり、今の場合、\xName が展開可能であったとしても、\csname… の一回展開は飽くまで \xName となります。 

  15. \newif により作成される \if-トークンのことをスイッチ(switch)といいます。 

  16. 今時のLaTeXは全て、e-TeX拡張をもつエンジンの上で動作しています。 

17
14
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
17
14