\NewDocumentCommand vs \newcommand vs \NewExpandableDocumentCommand(定义文本容器)

\NewDocumentCommand vs \newcommand vs \NewExpandableDocumentCommand(定义文本容器)

他的回答对于我最近的一个问题,@egreg 建议\newcommand不要\NewDocumentCommand为文本定义容器。我知道这个问题(10年了!)在哪里:

  • Joseph Wright 回答说:“从概念上讲,\NewDocumentCommand它旨在让‘软件包作者’定义命令”。但是,用它定义可选参数(甚至是带星号的变体)要简单得多,\NewDocumentCommand我不明白为什么不建议文档作者使用它。

  • Ulrike Fischer 回答说:“用 定义的宏\NewDocumentCommand是健壮的,当移动到 时,它们不会扩展”。但她给出的例子.toc的问题\NewDocumentCommand那里可以通过以下方式避免\NewExpandableDocumentCommand

      \documentclass{article}
      % \usepackage{xparse}                          % <- useless nowadays
    
      \begin{document}
      \NewDocumentCommand\testA{}{ABC}
      \newcommand\testB{ABC}
      \NewExpandableDocumentCommand{\testC}{}{ABC}   % <- added by me
    
      \tableofcontents
    
      \RenewDocumentCommand\testA{}{CDE}
      \renewcommand\testB{CDE}
      \RenewExpandableDocumentCommand{\testC}{}{CDE} % <- added by me
    
      \section{\testA, \testB
      , \testC                                       % <- added by me
      }
    
      \end{document}
    

在此处输入图片描述

因此我想知道为什么不使用:

  • \NewDocumentCommand(和朋友)作为文档作者?
  • \NewExpandableDocumentCommand为文本定义一个容器?

\NewExpandableDocumentCommand我同意在同一个序言中混合使用s 和 s并不理想\NewDocumentCommand,前者变量 (即文本容器,即用作存储的宏),后者用于 命令。但混合\newcommands 和\NewDocumentCommands 看起来会更糟。

此外,这个答案也赞成\NewDocumentCommand\NewExpandableDocumentCommand

所以我知道这已经被讨论过了,但是,既然xparse现在已经在内核中了,我想知道是否仍然有支持\NewDocumentCommand\NewExpandableDocumentCommand反对文档作者的强有力的论据(我倾向于在我的 LaTeX 课程中推广它们)。

编辑

(这里有一些可能解释当前问题的背景信息。)

我关心的是要教给 LaTeX 的初学者或中级用户什么。我曾经\newcommand{\dst}{Dostoïevsky}以 LaTeX 的一个好功能为例,教给需要写一篇关于陀思妥耶夫斯基的长篇报告的学生。当然,我并没有止步于此:对于历史系的学生,我曾经教过:

  • \newcommand{\century}[1]{\textsc{#1}\ieme{}~siècle}(抱歉,法语中“siècle”是“世纪”,并且带有babel-french\ieme
  • 甚至因为在法语中,只有1\newcommand{\century}[2][\ieme]{\textsc{#2}#1~siècle}世纪才有例外(忽略负世纪):。\century[\ier]{i}

我希望找到一种方法来替代这种定义变量/命令的更现代、更灵活、更统一的方法:“ xparse”方式。

答案1

如果您想要一个可以在“完全扩展”上下文中通过扩展传递的文本容器,那么这\NewDocumentCommand是不可能的,因为它定义了一个\protected命令。

因此,选择是在\NewExpandableDocumentCommand和之间\newcommand。后者具有很大的优势:传递所包含的文本仅需要一个扩展步骤。

此外,\New(Expandable)DocumentCommand旨在解析以比传统 LaTeX 编程更干净的方式处理参数,而不是作为 的万能替代品,后者将继续存在。如果您不想遵循下面描述的路径,\newcommand最好使用无参数宏(即容器)。\newcommand

更好的方法

实际上,我的建议是两者都不要用。

\ExplSyntaxOn
\NewDocumentCommand{\definecontainer}{mm}
 {
  \tl_new:c { l__denis_container_#1_tl }
  \tl_set:cn { l__denis_container_#1_tl } { #2 }
 }
\NewExpandableDocumentCommand{\usecontainer}{m}
 {
  \tl_use:c { l__denis_container_#1_tl }
 }
\ExplSyntaxOff

或者可能是一份财产清单:

\ExplSyntaxOn

\prop_new:N \l_denis_container_prop

\NewDocumentCommand{\definecontainer}{mm}
 {
  \prop_put:Nnn \l_denis_container_prop { #1 } { #2 }
 }

\NewExpandableDocumentCommand{\usecontainer}{m}
 {
  \prop_item:Nn \l_denis_container_prop { #1 }
 }
\ExplSyntaxOff

无论哪种情况,您都可以说\definecontainer{foo}{whatever}\usecontainer{foo}

后者的优点是,所传递的内容\prop_item:Nn不会在完全扩展的环境中进一步扩展。

实际应用

就像是\newcommand{\dst}{Dostoïevsky} 似乎简化生活,但事实并非如此。几个月后,同一篇文章的作者将不记得是什么\dst意思。

一个可能的更好的选择是使用\dostoievsky它作为名称,但缺点是你需要{}在它后面保留一个空间。

类似上述的策略可能会起到更好的作用。

% boilerplate code
\ExplSyntaxOn
\prop_new:N \g_denis_names_prop
\NewDocumentCommand{\definename}{mm}
 {
  \prop_gput:Nnn \g_denis_names_prop { #1 } { #2 }
 }
\NewExpandableDocumentCommand{\name}{m}
 {
  \prop_item:Nn \g_denis_names_prop { #1 }
 }
\ExplSyntaxOff

% define names
\definename{dostoievski}{Dostoïevsky}

在文档正文中,您可以使用

The Russian writer \name{dostoievsky} risked to be executed.

名字就在那里,明显的惯例是删除大写字母和变音符号。这样会更容易记住。

因为\century你可以做到

\documentclass{article}
\usepackage{fix-cm}
\usepackage[T1]{fontenc}
\usepackage[french]{babel}

\ExplSyntaxOn
\NewDocumentCommand{\century}{m}
 {
  % make the argument lowercase
  \textsc{\text_lowercase:n{#1}}
  % add the suffix
  \denis_century_suffix:n { #1 }
 }
\cs_new_protected:Nn \denis_century_suffix:n
 {
  \str_case_e:nnF { \str_foldcase:n { #1 } }
   { {i}{\ier} }
   { \ieme }
 }
\ExplSyntaxOff

\begin{document}

\century{i} \century{I} \century{xiv} \century{XIV}

\end{document}

在此处输入图片描述

比使用可选参数更复杂吗?也许吧,但它使生活变得容易得多。

答案2

在我的 LaTeX 书中,我将其放到\newcommand边栏和\NewExpandableDocumentCommand附录中,更喜欢使用\NewDocumentCommand(和\NewEnvironment) 来创建命令。

最大的限制一直认为tabularrayexpand=选项仅适用于用\def\newcommand¹ 定义的命令,但较新的版本放宽了该限制(现在我需要在手稿中添加一个部分,在检查选项名称时看到了这一点)。

我确实喜欢将参数规范放在自己的行上,因为\NewDocumentCommand这样在定义没有参数的命令时更不容易遗漏空规范。比较

\NewDocumentCommand{\dst}
   {}
   {Dostoïevsky}

\NewDocumentCommand{\dst}{}{Dostoïevsky}

  1. 除了用 定义的命令\newcommand具有可选参数外。

答案3

我们有两种情况来将 token 列表保存到 TeX 内存中并恢复它。第一种情况是定义没有参数的宏,第二种情况是使用 toks 寄存器:

                            save it:  \def\foo{text}    use it:   \foo
or:
declare it: \newtoks\foo    save it:  \foo={text}       use it:   \the\foo

\edef第一种情况如果完全可展开(在,上下文中\write),第二种情况只展开到text,但如果text包括可扩展的控制序列,则它们不会在\edef,\write上下文中展开。例如

\newtoks\foo  \foo={text: \the\dimen0 }  \edef\bar{... \the\foo ...}

然后\bar是一个带有主体的宏:... text: \the\dimen0 ...

此外,我们可以使用前缀\protectedbefore\def\foo来禁止\foo\edef,\write上下文中扩展:

\protected\def\foo{text}  \edef\bar{... \foo ...}

然后\bar是一个带有主体的宏:... \foo ...

请注意,前缀也\long可以用在前面\def,但定义无参数宏时,前缀的用法无关紧要。只有\ifx在长宏和非长宏之间才有区别,即使它们没有参数。

LaTeX 提供了大量的宏,可以扩展到这些原始构造(为了让世界变得更加复杂?)。我们可以尝试使用它们,然后查看声明的控制序列的含义:

\tt \parindent=0pt \parskip=10pt
\def\foo{text}               def: \meaning\foo     \par
\newtoks\foo  \foo={text}    toks: \meaning\foo    \par
\protected\def\foo{text}     protected def: \meaning\foo   \par

\let\foo=\undefined
\newcommand\foo{text}        newcommand: \meaning\foo   \par
\renewcommand\foo{text}      renewcommand: \meaning\foo  \par

\DeclareRobustCommand\foo{text}  DeclareRobustCommand: \meaning\foo  \par

\let\foo=\undefined
\NewDocumentCommand\foo{}{text}  NewDocumentCommand: \meaning\foo  \par
\let\foo=\undefined
\NewExpandableDocumentCommand\foo{}{text}  NewExpandableDocumentCommand: \meaning\foo  \par

结果是:

定义

\DeclareRobustCommand请注意,由、\NewDocumentCommand 和声明的宏\NewExpandableDocumentCommand不会直接展开到其声明的宏体中,但有更复杂的方法可以到达声明的宏体。当我们仅使用 时,声明的宏体是不可见的\meaning\foo。跟踪此类宏(使用正\tracingmacros)是非常不方便。

LaTeX 的 Expl3 语法中还有许多其他宏可以做相同或类似的事,但我不想深入研究它们。它们最终都会运行\def或处理标记寄存器。

相关内容