2016年8月24日

あるコントロールシークエンス\testの後ろにコードを追加したい,ということはよくある.つまり\test<\testの一回展開><何かコード>という形に定義し直したい.これは

\expandafter\def\expandafter\test\expandafter{\test <何かコード>}
とすれば良い.e-TeX拡張が使える場合は,\unexpandedを使い次のようにもかける.(以前ZRさんに教えてもらった.)

\def\onceexpand#1{\expandafter\unexpanded\expandafter{#1}}
\edef\test{\onceexpand{\test}\unexpanded{<何かコード>}}

一回展開にしたいところを\onceexpandで囲めばいいだけなので,かなり便利.これを踏まえて,\testが引数を一つとる場合にやろうと次のようにしてみた.

\documentclass{article}
\begin{document}
\def\onceexpand#1{\expandafter\unexpanded\expandafter{#1}}
\def\test#1{(#1)}
\edef\test#1{\onceexpand{\test{#1}}[#1]}
\test{A}
\end{document}

コンパイルしてみる.

! You can't use `macro parameter character #' in horizontal mode.
\test #1->(##
             1)[#1]
l.6 \test{A}

あれ,詰まった.上に出ているけど,\testの中身が(##1)[#1]となっているようだ.(#1)[#1]を想定していたのだが.どうも,\unexpandedで囲むと#が増えるみたい.

\edef\test#1{\unexpanded{#1}} % \test: #1->##1

何故増えるのかわからないけど,増えるのだと思ってみて次のようにしてとりあえず解決.

\def\onceexpand#1{\expandafter\unexpanded\expandafter{#1}}
\def\test#1{(#1)}
\edef\deftest{%
  \unexpanded{\def\test#1}{\onceexpand{\test{#1}}\unexpanded{[#1]}}%
}
\deftest

これは以下と等価ということなんだろう.

\def\deftest{%
  \def\test##1{(##1)[##1]}%
}
\deftest

で,なんで増えるのかわからんと思ってつぶやいたらつぶやき返しされたのでメモ.まずe-TeXのマニュアルには,「\unexpanded\the<トークンレジスタ>と同じ」とある.ソース(etex.ch)を見ても,そもそも実装が\the<トークンレジスタ>を使っているように見えるし,

\toks0={#1}
\edef\test#1{\the\toks0}
\show\test
としても,##1となる.

で,なんでこれが増えるかなんだけど,実際は増えていない.\defは次のことをするようだ.以下,#はカテゴリーコード6を常に持つとする.

  • #1という並びを,「一つ目の引数を表すトークン」(?)にする.
  • ###に変換する.よって,##1は「トークン#」と「トークン1」の並びになる.

従って,

\def\test#1{##1}
という定義を考えると,上の理由から\testの中身は「トークン#」と「トークン1」の並びとなる.

一方,先ほどの

\toks0={#1}
\edef\test#1{\the\toks0}
は,まず\toks0の代入の段階では\defは関与していないので上のような変換は行われない.よって\toks0の中身は「トークン#」と「トークン1」の並びである.\edef内の\the\toks0は中身のトークン列に展開されるだけなので,結果\testの中身は「トークン#」と「トークン1」の並びとなる.さっきと同じ.つまり二つの方法で定義される\testは同一である.

では\def\test#1{##1}で定義した\testの中身を見ようと\show\testしたらどうなるか.これは#1 -> ##1と出てきて欲しいのが人情だろう.これを実現するために,\showで出す時はさきほどの\defの逆をする,つまり

  • 「一つ目の引数を表すトークン」を#1
  • ###
変換するようだ.なので,#が一つ増えた,と思ってしまったということのようだ.

トークン代入の方だと納得するけど,\unexpandedだとなんか違うようなーなんて思ったけど,「#1という並びを,『一つ目の引数を表すトークン』にする」という「展開」を抑制しているのだと思うと納得できる気もしてきた.

2 件のコメント:

  1. > あるコントロールシークエンス\testの後ろにコードを追加したい
    > \testが引数を一つとる場合に

    ご存知なのではとは思いますが,etoolbox には \apptocmd というこの目的のためのマクロがあります.
    今年の初めあたりに,これらパッチ系コマンド本体である \etb@hooktocmd を活用しようとして
    色々試していたことがあって,そのときに # の問題と闘った覚えがあります.
    最終的に \unexpanded については同じく
    > 「#1という並びを,『一つ目の引数を表すトークン』にする」という「展開」を抑制している
    という理解に至ったような.ただ,\show の動作などそこまで深く考えてはいなかったなあ.

    返信削除
  2. 恥ずかしいことに,\apptocmdは引数無しだけだと思っていました.引数の数を自動的に取得するんですね,変態だ…….etoolboxのマニュアル改めて読んでおこう.
    まぁ今回は,引数無しならば\unexpandedの力を借りれば余裕→引数あっても余裕だろう→はまったという流れなので,パッケージとかを探すことはしなかったのですが.

    > ただ,\show の動作などそこまで深く考えてはいなかったなあ.
    Twitter素晴らしいです

    返信削除

コメントの追加にはサードパーティーCookieの許可が必要です