在他的回答对于我最近的一个问题,@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
,前者变量
(即文本容器,即用作存储的宏),后者用于
命令。但混合\newcommand
s 和\NewDocumentCommand
s 看起来会更糟。
此外,这个答案也赞成\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
) 来创建命令。
最大的限制有一直认为tabularray
的expand=
选项仅适用于用\def
或\newcommand
¹ 定义的命令,但较新的版本放宽了该限制(现在我需要在手稿中添加一个部分,在检查选项名称时看到了这一点)。
我确实喜欢将参数规范放在自己的行上,因为\NewDocumentCommand
这样在定义没有参数的命令时更不容易遗漏空规范。比较
\NewDocumentCommand{\dst}
{}
{Dostoïevsky}
和
\NewDocumentCommand{\dst}{}{Dostoïevsky}
- 除了用 定义的命令
\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 ...
。
此外,我们可以使用前缀\protected
before\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
或处理标记寄存器。