我对 非常陌生expl3
。我曾尝试编写一个命令来打印内积,采用逗号分隔的列表。如果列表仅包含一个元素,则打印 〈elem1, elem1〉,否则打印 〈elem1, ..., elemn〉。以下是代码
\documentclass{article}
\usepackage{mathtools}
\usepackage{xparse}
\DeclarePairedDelimiterX{\inner}[1]{\langle}{\rangle}{\innerp{#1}}
\ExplSyntaxOn
\tl_new:N \l_innerp_elem_int
\NewDocumentCommand{\innerp}{m}
{
\tl_set:Nf \l_innerp_elem_int { \clist_count:n { #1 } }
\int_compare:nNnTF { \l_innerp_elem_int } { = } { 1 }
{ \innerp_print_list:n { #1, #1 } }
{ \innerp_print_list:n { #1 } }
}
\clist_new:N \l_innerp_arguments_clist
\cs_new_protected:Nn \innerp_print_list:n
{
\clist_set:Nn \l_innerp_arguments_clist { #1 }
\clist_use:Nn \l_innerp_arguments_clist { , }
}
\ExplSyntaxOff
\begin{document}
$\inner{\cdot}$, $\inner{v,w}$
\end{document}
到目前为止,它运行良好。我的问题是,这是否是惯用/正确的方法,或者有什么可以改进的?例如,如果使用包含两个以上元素的列表调用它,我想阻止/打印错误消息。
答案1
注意:此行类型不匹配:
\tl_new:N \l_innerp_elem_int
您的变量有_int
后缀;因此应使用 进行声明\int_new:N
。在这里,您声明了一个标记列表变量,其名称违反了惯例,让人以为它是一个整数变量。您很幸运它能正常工作,因为tl
变量是作为非受保护的宏实现的,因此它们在整数表达式(例如 的第一个和第三个参数)中自然扩展\int_compare:nNnTF
。
出于同样的原因,\tl_set:Nf \l_innerp_elem_int {...}
读取时是错误的,但实际上“没问题”,因为你的\l_innerp_elem_int
是一个tl
变量!
我认为后面\clist_set:Nn
紧跟着的\clist_use:Nn
不是必要的(见下面的代码)。最棘手的部分是错误消息的定义,因为我想打印\inner
后面跟着的,:
中间没有空格。这并不那么简单,因为:
是信根据\ExplSyntaxOn
制度(即,类别代码为 11)。定义此错误消息的另一种方法是:
\cs_generate_variant:Nn \msg_new:nnn { nne }
\msg_new:nne { innerp } { too-many-operands }
{ Too~many~operands~in~argument~'#1'~of~\c_backslash_str inner:~#2. }
-type参数e
基于\expanded
2019 年或更高版本的 TeX 引擎,与x
-type 参数相反,它不需要将#
标记加倍。
其余代码(主要是使用 的项数 switch-case \int_case:nnF
)非常简单,如您所见。如其文档旁边的星号所示界面3.pdf,\clist_count:n
是完全可扩展的;这就是它在 的第一个参数中起作用的原因\int_case:nnF
。
\documentclass{article}
\usepackage{mathtools}
\usepackage{xparse}
\DeclarePairedDelimiterX{\inner}[1]{\langle}{\rangle}{\innerp{#1}}
\ExplSyntaxOn
% Replace 'innerp' with the name of your package (or something likely to be
% unique).
\msg_new:nnn { innerp } { too-many-operands }
{ Too~many~operands~in~argument~'#1'~of~\tl_to_str:n { \inner: } #2. }
\cs_generate_variant:Nn \msg_error:nnnn { nnnx }
\NewDocumentCommand \innerp { m }
{
\int_case:nnF { \clist_count:n {#1} }
{
{ 1 } { #1, #1 }
{ 2 } {#1}
}
{
\msg_error:nnnx { innerp } { too-many-operands }
{#1} { \clist_count:n {#1} }
}
}
\ExplSyntaxOff
\begin{document}
$\inner{\cdot} - \inner{v,w} + \inner{u} = \inner{u,v}$
% Package innerp Error: Too many operands in argument 'u,v,w' of \inner: 3.
% $\inner{u,v,w}$
\end{document}
具有可自定义分隔符的变体
上述代码的以下变体稍微复杂一些,允许您在运行时使用第二个可选参数选择内部分隔符\inner
(第一个可选参数给出分隔符大小 - 参见下面的第二个示例)。
\documentclass{article}
\usepackage{mathtools}
\usepackage{xparse}
\ExplSyntaxOn
% Replace 'innerp' with the name of your package (or something likely to be
% unique).
\msg_new:nnn { innerp } { too-many-operands }
{ Too~many~operands~in~argument~'#1'~of~\tl_to_str:n { \inner: } #2. }
\cs_generate_variant:Nn \msg_error:nnnn { nnnx }
% #1: separator used in the output
% #2: comma-list of items
\cs_new_protected:Npn \innerp_print_list:nn #1#2
{
\clist_set:Nn \l_tmpa_clist {#2}
\clist_use:Nn \l_tmpa_clist {#1}
}
\NewDocumentCommand \innerp { O{,} m }
{
\int_case:nnF { \clist_count:n {#2} }
{
{ 1 } { \innerp_print_list:nn {#1} { #2, #2 } }
{ 2 } { \innerp_print_list:nn {#1} {#2} }
}
{
\msg_error:nnnx { innerp } { too-many-operands }
{#2} { \clist_count:n {#2} }
}
}
\DeclarePairedDelimiterX{\explicitInner}[2]{\langle}{\rangle}{\innerp[#1]{#2}}
\NewDocumentCommand \inner { O{} O{,} m }
{
\explicitInner [#1] {#2} {#3}
}
\ExplSyntaxOff
\begin{document}
\[ \inner{\cdot} - \inner{v,w} + \inner{u} = \inner{u,v} \]
\[ \inner[][\delimsize\vert]{u,u} =
\inner[\Big][\delimsize\vert]{\, \sum_{i=1}^{n} x_i e_i} \]
% Package innerp Error: Too many operands in argument 'u,v,w' of \inner: 3.
% $\inner{u,v,w}$
\end{document}
答案2
我意识到这是一个expl3
疑问,但我认为我还是会添加传统的 2e 解决方案:
\documentclass{article}
\usepackage{listofitems}
\newcommand\inner[1]{\readlist\innerlist{#1}\langle%
\ifnum\innerlistlen=1\relax#1,#1\else#1\fi\rangle}
\begin{document}
$\inner{\cdot}$, $\inner{v,w}$
\end{document}
补充
OP 在评论中要求提供错误捕获和指定输出分隔符的能力。在这里,后者是通过可选参数实现的。前者很容易实现。
\documentclass{article}
\usepackage{listofitems}
\newcommand\inner[2][,]{\readlist\innerlist{#2}\langle%
\ifnum\innerlistlen=1\relax#2#1#2\else
\ifnum\innerlistlen>2\textrm{Error!}\else
\innerlist[1]#1\innerlist[2]\fi\fi\rangle}
\begin{document}
$\inner{\cdot}$, $\inner[|]{v,w}$, $\inner{v,w,z}$
\end{document}
答案3
由于您最多有两个参数,因此无需检查长度。\SplitArgument{1}{,}
如果提供两个以上的项目,您将收到错误。
我还添加了一个简写形式,用简单的句点来表示空白。
\documentclass{article}
\usepackage{mathtools}
\usepackage{xparse}
\DeclarePairedDelimiterX{\inner}[1]{\langle}{\rangle}{\innerinner{#1}}
\ExplSyntaxOn
\tl_new:N \l_innerp_elem_tl
\NewDocumentCommand{\innerinner}{>{\SplitArgument{1}{,}}m}
{
% \SplitArgument passes two braced items; if only one item
% is passed, the second braced item will pass the “novalue” test
\euklid_inner:nn #1
}
\cs_new_protected:Nn \euklid_inner:nn
{
\tl_if_novalue:nTF { #2 }
{
\__euklid_inner:nn { #1 } { #1 }
}
{
\__euklid_inner:nn { #1 } { #2 }
}
}
\cs_new_protected:Nn \__euklid_inner:nn
{
\str_if_eq:nnTF { #1 } { . } { \,{\cdot}\, } { #1 }
,
\str_if_eq:nnTF { #2 } { . } { {\cdot}\, } { #2 }
}
\ExplSyntaxOff
\begin{document}
$\inner{.}+\inner{.,w}+\inner{v,.}+\inner{v}+\inner{v,w}$
\bigskip
$\inner[\big]{.}+\inner[\Big]{.,w}+\inner[\bigg]{v,.}
+\inner[\Bigg]{v}+\inner*{\dfrac{v}{2},w}$
\end{document}