[やりたいこと] \Aと書いたら、何も表示しない。
\A[hoge]と書いたら、A+hogeと表示する。
マクロが欲しい。
[ソース]
\newcommand{\A}[1][]{\if#1 {A+#1} \else \fi}
[本文とその結果]
(本文) \A[hoge], \A
(結果) h A+hoge,
[問題点]
\A[hoge] のとき、欲しいA+hogeの前に、引数の1文字目hが表示されてしまう。
上のソースのどこが間違っているのか、もっとうまいやり方があるのか?
どなたか、よろしくご教授ください。
そもそもあなたは「TeX での条件処理」について
「まともに」書かれた解説書をお読みになったことはあるのですか?
あなたが提示なさったコードは
「\if はその直後の *2 個* のトークン(通常は文字トークン)の
文字コードを比較する」ということをふまえたコードには
残念ながら見えません.
# それ以前の問題として,あなたが提示なさった定義を用いた場合,
# (少なくとも,当方の環境でテストした限り)
# \A[hoge],\A のどちらも何も出力しません.
ここでは,
\def\A{\@ifnextchar[\@A\relax}
\def\@A[#1]{A+#1}
のようなコードを検討してくださいと述べるのは簡単ですが,
むしろ TeX Wiki の「TeX の本」で挙げられているような解説書
(ただし,LaTeX の入門書のレベルではなく
マクロ作成を主要なテーマとしているもの,
あるいは plain TeX に関して深入りしているもの)を
精読することをお勧めすべきでしょう.
「まともに」書かれた解説書をお読みになったことはあるのですか?
あなたが提示なさったコードは
「\if はその直後の *2 個* のトークン(通常は文字トークン)の
文字コードを比較する」ということをふまえたコードには
残念ながら見えません.
# それ以前の問題として,あなたが提示なさった定義を用いた場合,
# (少なくとも,当方の環境でテストした限り)
# \A[hoge],\A のどちらも何も出力しません.
ここでは,
\def\A{\@ifnextchar[\@A\relax}
\def\@A[#1]{A+#1}
のようなコードを検討してくださいと述べるのは簡単ですが,
むしろ TeX Wiki の「TeX の本」で挙げられているような解説書
(ただし,LaTeX の入門書のレベルではなく
マクロ作成を主要なテーマとしているもの,
あるいは plain TeX に関して深入りしているもの)を
精読することをお勧めすべきでしょう.
二つ上に書き込まれた 08:55の匿名の方、
ちゃんと読んでますか?
その上に書き込まれた 19:59の匿名の方の回答には、
「本を読め」という一般的なアドバイスだけではなくて、
手直し済みの具体的なコードもありますよね。
「「本を読め」ならこんなFAQの存在意義は無いのでは?」というのは、中傷もいいとこですよ。
で、元の質問者からは音沙汰ないですが、
マクロ作成上のお題として面白かったので別解を上げてみます。展開可能なようにしたつもりです。
\documentclass{article}
\makeatletter
\newcommand\A[1][gobble]{\csname @@A@#1\endcsname A+#1\@@A@@gobble}
\def\@@A@gobble#1\@@A@@gobble{}
\def\@@A@@gobble{}
\makeatother
\begin{document}
\A[hoge],\A
\end{document}
## 発言にID番号のようなものがないので、言及しづらいですね。
ちゃんと読んでますか?
その上に書き込まれた 19:59の匿名の方の回答には、
「本を読め」という一般的なアドバイスだけではなくて、
手直し済みの具体的なコードもありますよね。
「「本を読め」ならこんなFAQの存在意義は無いのでは?」というのは、中傷もいいとこですよ。
で、元の質問者からは音沙汰ないですが、
マクロ作成上のお題として面白かったので別解を上げてみます。展開可能なようにしたつもりです。
\documentclass{article}
\makeatletter
\newcommand\A[1][gobble]{\csname @@A@#1\endcsname A+#1\@@A@@gobble}
\def\@@A@gobble#1\@@A@@gobble{}
\def\@@A@@gobble{}
\makeatother
\begin{document}
\A[hoge],\A
\end{document}
## 発言にID番号のようなものがないので、言及しづらいですね。
別件で検索中に引っかかって来ました。
私と同じことを調べようとした人向けに,追記しておきます。
> \newcommand{\A}[1][]{\if#1 {A+#1} \else \fi}
マクロの中身は [\if][#1][ ][{A+#1}][ ][\else][\fi] となっています。
つまり,"#1" と " "(半角スペース) を比較しているため上手くいっていません。
試しに \A[ ] とすれば,そのことが確認できるでしょう。
また,匿 名 さんの回答にある
> \def\A{\@ifnextchar[\@A\relax}
> \def\@A[#1]{A+#1}
> \def\@A[#1]{A+#1}
> のようなコードを検討してください
というのは,k-suke さんの要求通りに完全に動くマクロをつくろうとすると
「\newcommand」を自分で定義する知識が必要となるよという話です。
TeXブックの中にも,ヒントとなりそうなコードが出てきますので,
本気で興味があるならば調べてみるとよいでしょう。
答だけクレクレというのであれば,以下で十分でしょう。
\newcommand{\A}[1][\relax]{\if#1\relax{}\else{A+#1}\fi}%
※\A[\relax] のときのみ不正動作します。以下に,実験の経過報告。それぞれ,\if と \ifx を並べてあります。
マクロに登録してから比較(一番堅実)
\newcommand{\A}[1][\relax]{\def\tmpA{#1}\def\tmpB{\relax}\if\tmpA\tmpB{}\else{A+#1}\fi}% ○
\newcommand{\A}[1][\relax]{\def\tmpA{#1}\def\tmpB{\relax}\ifx\tmpA\tmpB{}\else{A+#1}\fi}% ○
\newcommand{\A}[1][\relax]{\def\tmpA{#1}\def\tmpB{\relax}\if\tmpA\tmpB{}\else{A+#1}\fi}% ○
\newcommand{\A}[1][\relax]{\def\tmpA{#1}\def\tmpB{\relax}\ifx\tmpA\tmpB{}\else{A+#1}\fi}% ○
[]とするとデフォルトがNULとなり,一見上手くいきますが,"\A[]" --> "" となってしまいます。
\newcommand{\A}[1][]{\def\tmpA{#1}\def\tmpB{}\if\tmpA\tmpB{}\else{A+#1}\fi}% ×
\newcommand{\A}[1][]{\def\tmpA{#1}\def\tmpB{}\ifx\tmpA\tmpB{}\else{A+#1}\fi}% △
\newcommand{\A}[1][]{\def\tmpA{#1}\def\tmpB{}\if\tmpA\tmpB{}\else{A+#1}\fi}% ×
\newcommand{\A}[1][]{\def\tmpA{#1}\def\tmpB{}\ifx\tmpA\tmpB{}\else{A+#1}\fi}% △
\if で比較するならば,\relax は直接書いても大丈夫のようです。
\newcommand{\A}[1][\relax]{\def\temp{#1}\if\temp\relax{}\else{A+#1}\fi}% ○
\newcommand{\A}[1][\relax]{\def\temp{#1}\ifx\temp\relax{}\else{A+#1}\fi}% ×
\newcommand{\A}[1][\relax]{\def\temp{#1}\if\temp\relax{}\else{A+#1}\fi}% ○
\newcommand{\A}[1][\relax]{\def\temp{#1}\ifx\temp\relax{}\else{A+#1}\fi}% ×
#1 もマクロであることを考えると,結論としては以下のようになります。
\newcommand{\A}[1][\relax]{\if#1\relax{}\else{A+#1}\fi}% ○
\newcommand{\A}[1][\relax]{\ifx#1\relax{}\else{A+#1}\fi}% ○
\newcommand{\A}[1][\relax]{\if#1\relax{}\else{A+#1}\fi}% ○
\newcommand{\A}[1][\relax]{\ifx#1\relax{}\else{A+#1}\fi}% ○
以上,誰かの参考になれば幸い。(何年経っていると・・・)
答合わせでもう少し調べたら,LaTeX の newcommand の空引数
としては \@empty を使うのが公式ルール(?)だそうです。
したがって,正解は次のようになります。
\makeatletter
\newcommand{\A}[1][\@empty]{\ifx\@empty#1{}\else{A+#1}\fi}%
\makeatother
\makeatother
もちろん,先の例と同様に \A[\@empty] では不正動作しますし,
そもそも空マクロなら不正となるので,例えば
\def\tmp{}%
\A[\tmp]
とかもやはり不正となります。
ただし,マクロの可読性(意図のわかりやすさ)を考えると
用意されている \@empty を使うのがよいと思います。
以上,追加でした。
ちょっと解説を読み直してみたら少し正確でないところがあるようです。
2022年 07月 27日(水曜日) 13:26
6行目> マクロの中身は [\if][#1][ ][{A+#1}][ ][\else][\fi] となっています。
7行目> つまり,"#1" と " "(半角スペース) を比較しているため上手くいっていません。
8行目> 試しに \A[ ] とすれば,そのことが確認できるでしょう。
マクロ定義を眺めているだけだと「"#1" と " "(半角スペース) を比較している」ような気がしてしまいますが、
実際には「マクロ展開時に #1 のところに取り込まれた引数トークン列から得られる文字列の先頭2文字」が比較されます。
なので例えば、引数の先頭文字をだぶらせて \A[hhoge] としても true になってしまうことに注意が必要ですね。
20行目> \newcommand{\A}[1][\relax]{\if#1\relax{}\else{A+#1}\fi}%
21行目> ※\A[\relax] のときのみ不正動作します。
上記と同じ理由で、 \A[\relax] 以外にも \A[oops!] などでも「不正動作」となります。
この問題は \if#1\relax ではなくて \if\relax#1 の順番にするだけである程度緩和されますね。
25行目> マクロに登録してから比較(一番堅実)
26行目> \newcommand{\A}[1][\relax]{\def\tmpA{#1}\def\tmpB{\relax}\if\tmpA\tmpB{}\else{A+#1}\fi}% ○
27行目> \newcommand{\A}[1][\relax]{\def\tmpA{#1}\def\tmpB{\relax}\ifx\tmpA\tmpB{}\else{A+#1}\fi}% ○
\ifx のほうは良いですが、\if のほうは上記と同じ問題があります。
\if\tmpA\tmpB のところで \tmpA が展開されてしまうので
#1に取り込まれるトークンから生成される文字列の先頭2文字が同じであれば
\if の判定は true になりますから、「○」とは言い切れないかと。
以上です。細かくてすみません。お気を悪くなされないことを願います。
2022年 07月 27日(水曜日) 13:26
6行目> マクロの中身は [\if][#1][ ][{A+#1}][ ][\else][\fi] となっています。
7行目> つまり,"#1" と " "(半角スペース) を比較しているため上手くいっていません。
8行目> 試しに \A[ ] とすれば,そのことが確認できるでしょう。
マクロ定義を眺めているだけだと「"#1" と " "(半角スペース) を比較している」ような気がしてしまいますが、
実際には「マクロ展開時に #1 のところに取り込まれた引数トークン列から得られる文字列の先頭2文字」が比較されます。
なので例えば、引数の先頭文字をだぶらせて \A[hhoge] としても true になってしまうことに注意が必要ですね。
20行目> \newcommand{\A}[1][\relax]{\if#1\relax{}\else{A+#1}\fi}%
21行目> ※\A[\relax] のときのみ不正動作します。
上記と同じ理由で、 \A[\relax] 以外にも \A[oops!] などでも「不正動作」となります。
この問題は \if#1\relax ではなくて \if\relax#1 の順番にするだけである程度緩和されますね。
25行目> マクロに登録してから比較(一番堅実)
26行目> \newcommand{\A}[1][\relax]{\def\tmpA{#1}\def\tmpB{\relax}\if\tmpA\tmpB{}\else{A+#1}\fi}% ○
27行目> \newcommand{\A}[1][\relax]{\def\tmpA{#1}\def\tmpB{\relax}\ifx\tmpA\tmpB{}\else{A+#1}\fi}% ○
\ifx のほうは良いですが、\if のほうは上記と同じ問題があります。
\if\tmpA\tmpB のところで \tmpA が展開されてしまうので
#1に取り込まれるトークンから生成される文字列の先頭2文字が同じであれば
\if の判定は true になりますから、「○」とは言い切れないかと。
以上です。細かくてすみません。お気を悪くなされないことを願います。
北見さんが解説なさっておられているので、それで十分だと思うのですが、
微力ながらも、実際に動く例を以下に貼り付けます。
何かのご参考になればと存じます。
%#!lualatex
% やりたいこと] \Aと書いたら、何も表示しない。
% \A[hoge]と書いたら、A+hogeと表示する。
% マクロが欲しい。
% [ソース]
% \newcommand{\A}[1][]{\if#1 {A+#1} \else \fi}
%
% [本文とその結果]
% (本文) \A[hoge], \A
% (結果) h A+hoge,
\documentclass{jlreq}
\makeatletter
%% 通常の用途で、この\foo程度で十分だと、個人的に思います
% \newcommand*{\foo}[1][]{\bgroup
% \edef\foo@arg{#1}%
% \ifx\foo@arg\@empty\else
% foo+\foo@arg\relax
% \fi
% \egroup}
\newcommand*{\foo}[1][]{\bgroup
% \ifx#1\@empty\else
\edef\foo@arg{#1}%
\def\relax@sp{\relax }%
% (\meaning\foo@arg)
\ifx\foo@arg\@empty\else
\ifx\foo@arg\relax@sp\else
foo+\foo@arg\relax
\fi\fi
\egroup}
\def\fooopt#1{%
\ifx#1e\else\ifx#1r\relax\fi\fi}
% \renewcommand{\foo}[1][\relax]{\if#1\relax{}\else{foo+#1}\fi}%%=> NGでは?
% \renewcommand{\foo}[1][\relax]{\ifx#1\relax{}\else{foo+#1}\fi}%%=> NGでは?
% \renewcommand*{\foo}[1][\@empty]{\ifx\@empty#1{}\else{foo+#1}\fi}%%=> これでもNGでは?
\makeatother
\begin{document}
\begin{enumerate}
%%普通の空だけを想定する→\@empty
\item わたしの名前は\foo です。
\item わたしの名前は\foo[taro]です。
%%あまり使わないけど、\relaxも想定する
\def\tmp{\relax}
\item わたしの名前は\foo[\tmp]です。
\makeatletter\def\tmp{\@empty}\makeatother
\item わたしの名前は\foo[\tmp]です。
\item わたしの名前は\foo[\fooopt{e}]です。
\item わたしの名前は\foo[\fooopt{r}]です。
\item わたしの名前は\foo[\fooopt{o}]です。
\end{enumerate}
\end{document}
微力ながらも、実際に動く例を以下に貼り付けます。
何かのご参考になればと存じます。
%#!lualatex
% やりたいこと] \Aと書いたら、何も表示しない。
% \A[hoge]と書いたら、A+hogeと表示する。
% マクロが欲しい。
% [ソース]
% \newcommand{\A}[1][]{\if#1 {A+#1} \else \fi}
%
% [本文とその結果]
% (本文) \A[hoge], \A
% (結果) h A+hoge,
\documentclass{jlreq}
\makeatletter
%% 通常の用途で、この\foo程度で十分だと、個人的に思います
% \newcommand*{\foo}[1][]{\bgroup
% \edef\foo@arg{#1}%
% \ifx\foo@arg\@empty\else
% foo+\foo@arg\relax
% \fi
% \egroup}
\newcommand*{\foo}[1][]{\bgroup
% \ifx#1\@empty\else
\edef\foo@arg{#1}%
\def\relax@sp{\relax }%
% (\meaning\foo@arg)
\ifx\foo@arg\@empty\else
\ifx\foo@arg\relax@sp\else
foo+\foo@arg\relax
\fi\fi
\egroup}
\def\fooopt#1{%
\ifx#1e\else\ifx#1r\relax\fi\fi}
% \renewcommand{\foo}[1][\relax]{\if#1\relax{}\else{foo+#1}\fi}%%=> NGでは?
% \renewcommand{\foo}[1][\relax]{\ifx#1\relax{}\else{foo+#1}\fi}%%=> NGでは?
% \renewcommand*{\foo}[1][\@empty]{\ifx\@empty#1{}\else{foo+#1}\fi}%%=> これでもNGでは?
\makeatother
\begin{document}
\begin{enumerate}
%%普通の空だけを想定する→\@empty
\item わたしの名前は\foo です。
\item わたしの名前は\foo[taro]です。
%%あまり使わないけど、\relaxも想定する
\def\tmp{\relax}
\item わたしの名前は\foo[\tmp]です。
\makeatletter\def\tmp{\@empty}\makeatother
\item わたしの名前は\foo[\tmp]です。
\item わたしの名前は\foo[\fooopt{e}]です。
\item わたしの名前は\foo[\fooopt{r}]です。
\item わたしの名前は\foo[\fooopt{o}]です。
\end{enumerate}
\end{document}
> 何かのご参考になればと存じます。
> \newcommand*{\foo}[1][]{\bgroup
> % \ifx#1\@empty\else
> \edef\foo@arg{#1}%
> \def\relax@sp{\relax }%
> % (\meaning\foo@arg)
> \ifx\foo@arg\@empty\else
> \ifx\foo@arg\relax@sp\else
> foo+\foo@arg\relax
> \fi\fi
> \egroup}
% \renewcommand{\foo}[1][\relax]{\if#1\relax{}\else{foo+#1}\fi}%%=> NGでは?
% \renewcommand{\foo}[1][\relax]{\ifx#1\relax{}\else{foo+#1}\fi}%%=> NGでは?
ありがとうございます。
> % やりたいこと] \Aと書いたら、何も表示しない。
> % \A[hoge]と書いたら、A+hogeと表示する。
> % \A[hoge]と書いたら、A+hogeと表示する。
そもそものお題がこれだけなので,私は \A[] --> A+ と読みました。
要するに [~] がついた場合には問答無用で A+ をつけると解釈したため,
\A[\relax], \A[\@empty], \A[{}] なども A+ を表示することを目指しました。
> % \ifx#1\@empty\else
> \edef\foo@arg{#1}%
> \def\relax@sp{\relax }%
> % (\meaning\foo@arg)
> \ifx\foo@arg\@empty\else
> \ifx\foo@arg\relax@sp\else
> foo+\foo@arg\relax
> \fi\fi
> \egroup}
展開した中身が空なら A+ もつけない,という解釈ですね。
\@empty と \relax のそれぞれに対応するという考え方は面白いです。\if のネストではよくやらかすため何となく忌避していました。
ネストするとエラーになりますが,通常の用途なら問題ないでしょうね。
\item わたしの名前は\foo[\foo[taro]]です。
% \renewcommand{\foo}[1][\relax]{\if#1\relax{}\else{foo+#1}\fi}%%=> NGでは?
% \renewcommand{\foo}[1][\relax]{\ifx#1\relax{}\else{foo+#1}\fi}%%=> NGでは?
NGです。\foo[hhoge] とか,最初の2文字が等しいと誤動作します。
% \renewcommand*{\foo}[1][\@empty]{\ifx\@empty#1{}\else{foo+#1}\fi}%%=> これでもNGでは?
なぜか * が入ってますね…。
1文字目が \@empty でなければ問題はないですが・・・まあNGでしょうね。
マクロに定義してからの比較ではないため,\foo[\@empty TARO] とかがアウトになります。
やはり一旦マクロに定義してから \ifx で比較するのが正解のようです。
> マクロ定義を眺めているだけだと「"#1" と " "(半角スペース) を比較している」ような気がしてしまいますが、
> 実際には「マクロ展開時に #1 のところに取り込まれた引数トークン列から得られる文字列の先頭2文字」が比較されます。
> なので例えば、引数の先頭文字をだぶらせて \A[hhoge] としても true になってしまうことに注意が必要ですね。
完全に失念していました。
何かが理由で \ifx を使うようにしていたような気がしていたのでしたが,これでした。
今回のような用途では \ifx は基本的に不向きですね。
> この問題は \if#1\relax ではなくて \if\relax#1 の順番にするだけである程度緩和されますね。
そのようですね。例外的に \if でも(想定通りではないが)期待通り動いている例が
\newcommand{\A}[1][\relax]{\if\relax#1{}\else{A+#1}\fi}%
\relax と1文字目を比較し,\relax でなかった時点で2文字目から \else まで読み飛ばすため,
結果的に期待通り動いているように見えます。もちろん,1文字目しか比較していないので
\A[\relax T] とかは失敗します。
> 25行目> マクロに登録してから比較(一番堅実)
> 26行目> \newcommand{\A}[1][\relax]{\def\tmpA{#1}\def\tmpB{\relax}\if\tmpA\tmpB{}\else{A+#1}\fi}% ○
> 27行目> \newcommand{\A}[1][\relax]{\def\tmpA{#1}\def\tmpB{\relax}\ifx\tmpA\tmpB{}\else{A+#1}\fi}% ○
>
> \ifx のほうは良いですが、\if のほうは上記と同じ問題があります。
> \if\tmpA\tmpB のところで \tmpA が展開されてしまうので
> #1に取り込まれるトークンから生成される文字列の先頭2文字が同じであれば
> \if の判定は true になりますから、「○」とは言い切れないかと。
そうですね,\if の方は正しくないです。
> 以上です。細かくてすみません。お気を悪くなされないことを願います。
お陰様で根本的な勘違い(というか,典型的な「やりがちな間違い」で恥ずかし・・)に気付きました。
テストケースも不十分でしたね。
時間をかけて検証いただき,ありがとうございました。
> 実際には「マクロ展開時に #1 のところに取り込まれた引数トークン列から得られる文字列の先頭2文字」が比較されます。
> なので例えば、引数の先頭文字をだぶらせて \A[hhoge] としても true になってしまうことに注意が必要ですね。
完全に失念していました。
何かが理由で \ifx を使うようにしていたような気がしていたのでしたが,これでした。
今回のような用途では \ifx は基本的に不向きですね。
> この問題は \if#1\relax ではなくて \if\relax#1 の順番にするだけである程度緩和されますね。
そのようですね。例外的に \if でも(想定通りではないが)期待通り動いている例が
\newcommand{\A}[1][\relax]{\if\relax#1{}\else{A+#1}\fi}%
\relax と1文字目を比較し,\relax でなかった時点で2文字目から \else まで読み飛ばすため,
結果的に期待通り動いているように見えます。もちろん,1文字目しか比較していないので
\A[\relax T] とかは失敗します。
> 25行目> マクロに登録してから比較(一番堅実)
> 26行目> \newcommand{\A}[1][\relax]{\def\tmpA{#1}\def\tmpB{\relax}\if\tmpA\tmpB{}\else{A+#1}\fi}% ○
> 27行目> \newcommand{\A}[1][\relax]{\def\tmpA{#1}\def\tmpB{\relax}\ifx\tmpA\tmpB{}\else{A+#1}\fi}% ○
>
> \ifx のほうは良いですが、\if のほうは上記と同じ問題があります。
> \if\tmpA\tmpB のところで \tmpA が展開されてしまうので
> #1に取り込まれるトークンから生成される文字列の先頭2文字が同じであれば
> \if の判定は true になりますから、「○」とは言い切れないかと。
そうですね,\if の方は正しくないです。
> 以上です。細かくてすみません。お気を悪くなされないことを願います。
お陰様で根本的な勘違い(というか,典型的な「やりがちな間違い」で恥ずかし・・)に気付きました。
テストケースも不十分でしたね。
時間をかけて検証いただき,ありがとうございました。
# どこにぶら下げるべきか迷いますが,北見さんにお楽しみいただければ幸いです. \if でやろうとすると,誤動作する場合の対処が厄介ですね(\if は「展開」してしまうので). 「展開したあとで空であるか」を気にするとしても,いったん \edef(もしくは \protected@edef) でマクロに収めてから \ifx で \empty と比較すれば充分だとは思います. もっとも,次のような処理は可能です. \documentclass{article} \makeatletter \newcommand*\A[1][]{% %%% \remove@to@nnil をつけているのは,下記の例の 4 のような %%% 最初の \if での比較が「true」になってしまう場合に残るゴミを %%% 始末するため. \expandafter\remove@to@nnil \if\relax#1\relax\@nnil %%% 上の比較のみでは,引数#1の展開結果が \relax をはじめとする %%% たいていのコントロール・シークェンス(*)で始まる場合に %%% #1 が空と誤認識される(**)が…… %%% (*) \let\sp=^ のように何らかの文字のコピーにしたり, %%% \chardef で定義したりされる「何らかの文字の別名」は例外 %%% (**) \if は \ifx とは異なり,コントロール・シークェンスどうしは %%% 基本的には区別しないので. \if @#1@%%% もう1つ別の比較を入れればよい \else A+#1% \fi \else \@nnil%%% \remove@to@nnil の始末をつける A+#1% \fi} \makeatother \begin{document} \begingroup 1: \A, %%% 何も出ない 2: \A[\empty], %%% \empty を展開すると空文字列なので,何も出ない 3: \A[\relax], %%% A+\relax で,「A+」のみ見える 4: \A[\def\zzz{}], %%% A+\def\zzz{} で「A+」のみ見える 5: \A[arere], %%% もちろん,「A+arere」 6: \A[zzz] %%% もちろん,「A+zzz」 \endgroup \end{document}
しっぽ 愛好家 さん、お久しぶりです。
ご指名いただき面映ゆい限りです。
実をいうと、true の場合に残りのトークンを除去してしまうための
>> \expandafter\remove@to@nnil\if
という書き方を見たのは初めてで、
どうして自分はこんな書き方をしたことがなかったのだろうと
しばらくの間悩んでいたのでした。
false の場合に残りのトークンを捨ておくことはよくあるのですが、
それにしても true の時に困るはずなのに、おかしいなぁと。
それでよくよく思い起こしてみると、
\if を使う場合でも複数トークンをまとめて取り込んだりせずに一つずつ取り込んでチェックするか、
マクロに保存して\ifx を使うか、
\futurelet を使うことがほとんどだったからのようです。
気になって latex.ltx の中を探してみましたが、
\expandafter\....\if で残りのトークンを除去している例は見当たりませんでした。
「TeX by Topic」にも見当たりませんし、
「LaTeX2eプログラミング基礎解説」「実践解説」「独習LaTeX2e」にも載ってない・・・ですよね?
さて、今回の \A について私だったらどうするかと考えて、(半分ネタですが)
\newcommand\A[1][\begingroup\setbox0=\lastbox\endgroup\remove@to@nnil]{%
\hbox{A+}#1\remove@to@nnil\@nnil}
とやってみました。条件分岐をLaTeXコマンドのオプション引数にすっかり任せてしまう例です。
その代わりに「A+」が水平ボックスに入ってしまうのが難点ですが。
ボックスに入れずに水平リストにそのまま置くのはどうにも思いつかずに、ここまででした。
これだと
>> \A[\iftrue \relax \fi]
でもうまくいくようです。
あと、
>> 2: \A[\empty], %%% \empty を展開すると空文字列なので,何も出ない
は挙動が違って「A+」を出力するようになります。
## どうにかネタらしきものが書けたのでよかったです。
## ハードル高っ(汗)
ご指名いただき面映ゆい限りです。
実をいうと、true の場合に残りのトークンを除去してしまうための
>> \expandafter\remove@to@nnil\if
という書き方を見たのは初めてで、
どうして自分はこんな書き方をしたことがなかったのだろうと
しばらくの間悩んでいたのでした。
false の場合に残りのトークンを捨ておくことはよくあるのですが、
それにしても true の時に困るはずなのに、おかしいなぁと。
それでよくよく思い起こしてみると、
\if を使う場合でも複数トークンをまとめて取り込んだりせずに一つずつ取り込んでチェックするか、
マクロに保存して\ifx を使うか、
\futurelet を使うことがほとんどだったからのようです。
気になって latex.ltx の中を探してみましたが、
\expandafter\....\if で残りのトークンを除去している例は見当たりませんでした。
「TeX by Topic」にも見当たりませんし、
「LaTeX2eプログラミング基礎解説」「実践解説」「独習LaTeX2e」にも載ってない・・・ですよね?
さて、今回の \A について私だったらどうするかと考えて、(半分ネタですが)
\newcommand\A[1][\begingroup\setbox0=\lastbox\endgroup\remove@to@nnil]{%
\hbox{A+}#1\remove@to@nnil\@nnil}
とやってみました。条件分岐をLaTeXコマンドのオプション引数にすっかり任せてしまう例です。
その代わりに「A+」が水平ボックスに入ってしまうのが難点ですが。
ボックスに入れずに水平リストにそのまま置くのはどうにも思いつかずに、ここまででした。
これだと
>> \A[\iftrue \relax \fi]
でもうまくいくようです。
あと、
>> 2: \A[\empty], %%% \empty を展開すると空文字列なので,何も出ない
は挙動が違って「A+」を出力するようになります。
## どうにかネタらしきものが書けたのでよかったです。
## ハードル高っ(汗)
>false の場合に残りのトークンを捨ておくことはよくあるのですが、 >それにしても true の時に困るはずなのに、おかしいなぁと。 今回ああいうものを書いたのはそこが気になったからです. また,私自身,ああいう処理を行ったのは今回が初めて(で, 結局使いものにならないことが判明……)ですので, 媒体によらず,あのようなコードをほかのところで 書いたことはありません. \begingroup \setbox0=\lastbox \endgroup という処理については有名(?)な実例(*)がありますが, ここでは,A+ の部分について「要らないときに消す」という 逆転の発想ですね. # (*) \section 等の見出しの直後の段落の開始時の字下げの抑制処理 # (もちろん,字下げを行わない場合のみ)
元々の投稿が2008年のものであることは完全に無視して、今のLaTeXにおいて
「オプション引数を完全展開して空になるかという条件にしたもの」
を実装してみました。
\documentclass{article} %% \A[<text>] % <text> を完全展開した結果が空ならば何も出力せず, % 空でなければ“A+<text>”を出力する. \newcommand*\A[1][]{% \if\relax\detokenize\expandafter{\expanded{#1}}\relax\else A+#1% \fi} % おまけ %% \Ax[<text1>]{<text2>} (完全展開可能) % <text1> を完全展開した結果が空ならば空に展開され, % 空でなければ“<text2>+<text1>”に展開される. \NewExpandableDocumentCommand\Ax{O{}m}{% \if\relax\detokenize\expandafter{\expanded{#1}}\relax\else #2+#1% \fi} %※最終引数がオプションである命令は完全展開可能にできない. \begin{document} \begingroup 1: \A, %空 2: \A[\empty], %空 3: \A[\relax], % 4: \A[\def\zzz{}], % 引数が完全展開可能でない 5: \A[arere], 6: \A[\iftrue\relax\fi], 7: \A[\iftrue\else\fi], %空 8: \A[zzz] \endgroup \begingroup \edef\Result{% 1: \Ax{A}, %空 2: \Ax[\empty]{A}, %空 3: \Ax[\relax]{A}, % 4: \Ax[\def\zzz{}]{A}, 5: \Ax[arere]{A}, 6: \Ax[\iftrue\relax\fi]{A}, 7: \Ax[\iftrue\else\fi]{A}, %空 8: \Ax[zzz]{A} }\typeout{\meaning\Result} \endgroup \end{document}
ちなみに、元々の仕様である
「オプション引数が存在するかという条件のもの」
は次のように書けます。
\documentclass{article} %% \A[<text>] % <text> 引数が存在すれば“A+<text>”を出力し, % しなければ何も出力しない. % (もちろん完全展開可能でない.) \NewDocumentCommand\A{o}{% \IfValueT{#1}{A+#1}} \begin{document} \begingroup 1: \A, %引数無し 2: \A[\empty], 3: \A[\relax], 4: \A[\def\zzz{}], 5: \A[arere], 6: \A[\iftrue\relax\fi], 7: \A[\iftrue\else\fi], 8: \A[zzz] \endgroup \end{document}
あくまでも \newcommand にこだわってやる方向で色々試してみましたが,
何が与えられても A+ をつける,というのは簡単ではないようです。
やはり 匿 名 さんのコードがシンプルでいいですね。
\def\A{\@ifnextchar[\@A\relax}
\def\@A[#1]{A+#1}
\def\@A[#1]{A+#1}
ところで,LaTeX3 では \NewDocumentCommand というのが使えるらしく,
LaTeX2ε からは \usepackage{
xparse}
で行けるらしい。https://qiita.com/zr_tex8r/items/50168ad7087516c3e139
LaTeX3 は意識していませんでしたが,自宅の TeX Live 2020 環境では
パッケージ読込を指定しなくても使えるようです。組み込み?
引数指定が無い場合にもしっかり対応しています。
\NewDocumentCommand\A{o}{\IfValueT{#1}{A+#1}}%
すごいなと思う反面,使いこなすまでは中々難しそうです。
\newcommand では難しいケースが発生したときに,
都度調べながら使っていく感じになるのでしょうね。