Variable Inner Product Macro

I am very new to expl3. I have tried to write a command that prints the inner product, taking a comma-separated list. If the list only contains one element it prints 〈elem1, elem1〉, otherwise 〈elem1, ..., elemn〉. Here is the code

\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}


It works so far fine. My question is if this is idiomatic/the right approach or is there something that could be improved on? For example, I would like to prevent/print an error message if it is called with a list consisting of more than two elements.

Beware: you have a type mismatch in this line:

\tl_new:N \l_innerp_elem_int


Your variable has an _int suffix; it should therefore be declared with \int_new:N. Here, you declare a token list variable with a name that violates the conventions and makes people believe it is an integer variable. You are lucky it works because tl variables are implemented as non-protected macros, thus they naturally expand in integer expressions such as the first and third arguments of \int_compare:nNnTF.

For the same reason, \tl_set:Nf \l_innerp_elem_int {...} is faulty on reading, but actually “fine” because your \l_innerp_elem_int is a tl variable!

I don't think the \clist_set:Nn immediately followed by \clist_use:Nn is necessary (see my code below). The trickiest part is the definition of the error message because I want to print \inner followed by a : and no space in-between. This is not quite straightforward, because : is a letter under \ExplSyntaxOn régime (i.e., it has category code 11). Another way to define this error message would be:

\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. }


The e-type argument is based on \expanded on TeX engines from 2019 or later and, contrary to x-type arguments, doesn't require one to double the # tokens.

The rest of the code, mainly a switch-case on the number of items using \int_case:nnF, is quite straightforward, as you can see. As stated by the star next to its documentation in interface3.pdf, \clist_count:n is fully expandable; that is why it works in the first argument of \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}


Variant with a customizable separator

The following variant of the above code is a bit more sophisticated and allows you to choose the inner separator at runtime using the second optional argument of \inner (the first optional argument gives the delimiter size—see the second example below).

\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}


• Thank you ver much for this detailed answer! I have used the clist commands in order to be able to change the separator globally (e.g. \innerp{v,w} would print v \vert w). Is there a better way to achieve this? – EuklidAlexandria Apr 26 at 12:26
• You're welcome. Indeed, if you want to keep the ability of easily changing the separator at runtime, I think your \innerp_print_list:n function is okay. Minor nit: you should probably name the variable \l__innerp_arguments_clist (two underscores after l) because it I think you consider it as private. Or you could use \l_tmpa_clist, but when you do so, be very careful to use the result immediately: if there are intervening function calls (belonging to your code or to a package) that also use \l_tmpa_tl, they could interfere. However, calls to official expl3 functions are safe. – frougon Apr 26 at 12:38
• I've updated my answer to add a more sophisticated example that allows you to choose the separator at runtime using an optional argument. Hope this helps. – frougon Apr 26 at 13:25
• Thank you very much! – EuklidAlexandria Apr 29 at 8:53

Since you have at most two arguments, doing the checks about the length is not needed. With \SplitArgument{1}{,} you'll get an error if more than two items are supplied.

I also added a shorthand for denoting the blank with a simple period.

\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}


• Thank you very much for this short solution! I especially like the shortcut you have added. – EuklidAlexandria Apr 29 at 8:54

I realize this is an expl3 question, but I thought I would add the traditional 2e solution nonetheless:

\documentclass{article}
\usepackage{listofitems}
\ifnum\innerlistlen=1\relax#1,#1\else#1\fi\rangle}
\begin{document}
$\inner{\cdot}$, $\inner{v,w}$
\end{document}


SUPPLEMENT

The OP asks in a comment for error trapping and the ability to specify the output separator. Here, the latter is accomplished with an optional argument. The former is easily realized.

\documentclass{article}
\usepackage{listofitems}
$\inner{\cdot}$, $\inner[|]{v,w}$, $\inner{v,w,z}$

• Thank you for this short answer! I was not aware of this package. Is it possible to issue an error if more than two arguments are supplied? And can the separator be changed inside the definition, for example \inner{v,w} would print \langle v \vert w \rangle? – EuklidAlexandria Apr 26 at 12:10