为什么由 \newcommand 定义的带有可选参数的命令不可扩展?

为什么由 \newcommand 定义的带有可选参数的命令不可扩展?

在尝试修复嵌套宏重复调用自身的问题时,我尝试先扩展内部调用,然后再将其作为参数传递给外部调用。这失败了,因为宏是使用\newcommand可选参数定义的。所以我的问题是:

为什么某些东西没有被定义为\newcommand{\test}[2][]{}可扩展的?

有没有可扩展的替代方案?

这是一个简单的例子:

\documentclass{article}

\newcommand{\test}[2]{got #1 and #2}
\newcommand{\testopt}[2][nothing]{got #1 and #2}

\begin{document}

\edef\result{\test{one}{two}}
\result

\edef\result{\testopt{two}}
\result

\end{document}

答案1

正如 egreg 所解释的那样,对可选参数的完全稳健检查必须使用\futurelet并且不能扩展(事实上,\newcommand不能进行完全正确的检查:尝试\let\lbrack[\testopt\lbrack...使用你的定义\testopt,但可以修复它)。

扩展一下,有两种方法可以向前看:要么抓取未定界的参数,要么抓取定界的参数。第一种方法删除括号:\testopt{[}a]{b}将被误认为与相同\testopt[a]{b}。对于第二种方法,我们需要决定直到我们应该抓取哪些标记,并且这些标记必须存在。一种可能性是,由于的第二个参数\testopt是强制性的,强制用户将其放在括号中,并抓取直到第一个左括号。

  • 未定界参数:我们需要测试是否为空(\detokenize将所有 catcode 设置为1210,不同于 的 catcode $)。然后定义\testopt来获取一个参数,并将其与 进行比较[:如果它不包含[,则没有可选参数,并且#1是强制参数。否则,测试它是否单独在 中#1(稍微有点脏的代码以迎合 的情况\testopt{{}[}),在这种情况下我们认为有一个可选参数。

    \makeatletter
    \newcommand{\@ifstrempty}[1]{%
      \ifcat$\detokenize{#1}$%
        \expandafter\@firstoftwo
      \else
        \expandafter\@secondoftwo
      \fi}
    \newcommand{\testopt@do}[2]{got #1 and #2}
    \newcommand{\testopt}[1]{%
      \expandafter\@ifstrempty
      \expandafter{\testopt@i#1[}
        {\testopt@do{nothing}{#1}}
        {\testopt@ii{#1}}}
    \long\def\testopt@i#1[{}
    \newcommand{\testopt@ii}[1]{%
      \expandafter\expandafter\expandafter\@ifstrempty
      \expandafter\expandafter\expandafter
        {\expandafter\testopt@iii\detokenize{#1}}
        {\testopt@iv#1}
        {\testopt@do{nothing}{#1}}
    \long\def\testopt@iii#1[{#1}
    \long\def\testopt@iv[#1]{\testopt@do{#1}}
    
  • 分隔参数:抓取直到左括号,测试是否以 开头[

    \long\def\testopt#1#{%
      \@ifstrempty{#1}{\testopt@do{nothing}}
        {%
          \expandafter\@ifstrempty\expandafter{\testopt@i#1[}
            {\testopt@do{nothing}#1}
            {\testopt@iv#1}%
        }%
    }
    

答案2

带有可选参数的命令不能“完全扩展”,因为它需要查看后面的字符才能确定它是否是方括号。这种前瞻必须通过赋值来完成,\futurelet而赋值只能在“TeX 的胃里”完成。

有一些巧妙的技巧可以在特定情况下允许使用可选参数完全扩展的宏,但也有很多限制。

在 LaTeX 2.09 中,所有带有可选参数的宏都很脆弱;LaTeX2e 改变了这种情况,使通过定义的宏变得\newcommand坚固。无论如何,在 中使用它们\edef是无望的。

答案3

LaTeX 提供的常规可选参数使用\futurelet(即 over \@ifnextchar) 来向前查看下一个标记是否为[。这是一个不可扩展但必须执行的赋值。

etextools包提供\FE@testopt核心 LaTeX 宏的完全可扩展 (FE) 版本\@testopt,据我所知,由 定义的具有可选参数的宏使用\newcommand。然而,这有一个限制,即宏必须至少有一个强制参数,并且不能用 调用{[}

一般用法是:

\def\MacroWithOption#1{\FE@testopt{#1}{\MacroHasOption}{default value}}

请参阅第 6 节具有选项和修饰符的完全可扩展宏软件包手册的所有详细信息。它使用 eTeX\detokenize进行安全测试#1

您可以使用以下包像这样编写示例:

\documentclass{article}
\usepackage{etextools}

\makeatletter
\newcommand{\test}[2]{got #1 and #2}
\def\testopt#1{\FE@testopt{#1}{\test@opt}{nothing}}
\def\test@opt[#1]#2{got #1 and #2}
\makeatother


\begin{document}
\ttfamily
\edef\result{\test{one}{two}}
\meaning\result

\edef\result{\testopt{two}}
\meaning\result

\end{document}

得出:

macro:->got one and two
macro:->got nothing and two

我自己为我的新包实现了一个类似的完全可扩展的可选参数测试宏filemod。但是,它只用于接受简单的可选参数(单个数字),并且可能会因输入更复杂而中断。

答案4

答案带有可选参数的 LaTeX 命令请参阅新xparse包(来自 LaTeX3 项目)。它是

旨在替代 LaTeX 2ε\newcommand

并提出了一个\DeclareExpandableDocumentCommand承诺来解决其他答案中提出的问题。

代码

\DeclareExpandableDocumentCommand\foo{ O{} m }{ ... }

创建可扩展的变体

\newcommand\foo[2][]{ ... }

。 然而,

这对函数接受的参数的性质以及它实现的代码施加了许多限制。

此外,它不在“稳定”命令列表中,因此必须

被视为“实验性的”

可能会改变或者消失。

相关内容