4

According to the documentation of the package xparse the syntax of \NewExpandableDocumentCommand is:

\NewExpandableDocumentCommand ⟨function⟩ {⟨arg spec⟩} {⟨code⟩}

When using xparse's \NewExpandableDocumentCommand for defining an expandable command ⟨function⟩ - what is the exact rule for calculating the amount of expansion-steps it takes outgoing from ⟨function⟩⟨arguments⟩ to obtain the ⟨replacement-text⟩ which is formed by the tokens ⟨code⟩ with ⟨code⟩'s parameters #1, #2, etc replaced by the ⟨arguments⟩?

E.g., using \newcommand one can do

% LaTeX 2e
\newcommand\FirstAndSecond[2]{#1 and #2}
\expandafter\def\expandafter\test\expandafter{\FirstAndSecond{A}{B}}
\show\test
\stop

and as expected with one \expandafter-chain one gets the message:

> \test=macro:
->A and B.
l.4 \show\test

If one uses \NewExpandableDocumentCommand instead of \newcommand and does

% LaTeX 2e
\RequirePackage{xparse}
\NewExpandableDocumentCommand\FirstAndSecond{mm}{#1 and #2}
\expandafter\def\expandafter\test\expandafter{\FirstAndSecond{A}{B}}
\show\test
\stop

, then one gets the message

> \test=macro:
->\__xparse_start_expandable:nNNNNn {mm}\FirstAndSecond  \FirstAndSecond  \Firs
tAndSecond code ?{\__xparse_expandable_grab_m:w \__xparse_expandable_grab_m:w }
{A}{B}.
l.5 \show\test

. Obviously commands defined in terms of xparse's \NewExpandableDocumentCommand need more expansion-steps.

It seems using \romannumeral-expansion one can work around this without knowing the exact rules:

% LaTeX 2e
\RequirePackage{xparse}
\csname @ifdefinable\endcsname\stopromannumeral{\chardef\stopromannumeral=`\^^00}%
% !!! The call to \FirstAndSecond must always be preceded by \romannumeral !!!
\NewExpandableDocumentCommand\FirstAndSecond{mm}{\stopromannumeral #1 and #2}
\expandafter\def\expandafter\test\expandafter{\romannumeral\FirstAndSecond{A}{B}}
\show\test
\stop
> \test=macro:
->A and B.
l.7 \show\test

But this question is not about weird workarounds but about exact rules. :-)

Ulrich Diez
  • 28,770
  • 1
    'No idea' - we really don't want people worrying about such things beyond low-level code (so definitely not for document commands). I'd also point out that \expanded makes such concerns basically irrelevant. – Joseph Wright Jul 13 '21 at 13:29
  • There is no hard-set rule other than “count the number of expansions for each argument type and hope for the best”. Also, as Joseph said, the implementation doesn't worry about that, and it may change which will break your counting, so you shouldn't rely on the number of expansions – Phelype Oleinik Jul 13 '21 at 13:32
  • @JosephWright Is it possible to integrate \romannumeral-expansion into \NewExpandableDocumentCommand as indicated in my workaround (or \expanded-expansion as indicated by you) so that the rule with expandable document commands will be that in any case obtaining the replacement text takes exactly two expansion-steps? Such a rule would relieve some worries for those users who write commands for the document level where expansion does plays a role (e.g. in alignments/tables - tabularray) no matter if you don't want people to worry about that or not. – Ulrich Diez Jul 13 '21 at 13:55
  • @PhelypeOleinik Is it possible to integrate \romannumeral-expansion into \NewExpandableDocumentCommand as indicated in my workaround (or \expanded-expansion as indicated by you) so that the rule with expandable document commands will be that in any case obtaining the replacement text takes exactly two expansion-steps? Such a rule would relieve some worries for those users who write commands for the document level where expansion does plays a role (e.g. in alignments/tables - tabularray) no matter if you don't want people to worry about that or not. – Ulrich Diez Jul 13 '21 at 13:56
  • @JosephWright Ordinary users shall not worry about low-level things like expansion but they shall use \expanded and understand exactly what it does? – Ulrich Diez Jul 13 '21 at 13:59
  • 1
    @UlrichDiez Actually, with the current implementation, you can't know the number of expansion steps reliably. Using code from https://tex.stackexchange.com/a/492956/134574 I counted 4 steps for initialisation and 16 for cleaning up, plus 3 steps per m argument in your example. However, if I change your example to om, the function requires a different amount of steps if used as \FirstAndSecond[A]{B} or \FirstAndSecond[[A]]{B} or \FirstAndSecond{B} or ... because it processes what it sees differently. – Phelype Oleinik Jul 13 '21 at 14:03
  • 1
    @UlrichDiez No, of course normal users don't need to. But they also shouldn't be relying on detailed expansion behaviour of document commands. As it is, having to have an expandable/non-expandable split is not brilliant, but it's required (at least, unless we switch a lot of stuff around and perhaps require LuaTeX). – Joseph Wright Jul 13 '21 at 15:46
  • 1
    @UlrichDiez Even more than code-level commands, document ones should be 'sheep or goats': either they are safe inside \edef or \expanded, or they do their best to avoid any expansion (\protected/LaTeX robust/...). – Joseph Wright Jul 13 '21 at 15:47
  • 2
    I wonder if there's a concrete use case that would illustrate the need to know expansion detail: it's certainly possible to make it more tightly defined, but I think that would require a definite reason to change the current code. – Joseph Wright Jul 13 '21 at 16:13
  • @JosephWright Concrete use case: E.g., in my answer to the question How to retrieve order of zref's labels in the aux file an expandable user-level-macro \AllPreceedingAndFollowingLabels would not be possible without exact knowledge of the expansion-steps needed with \GetLabelNameFromLabelNumber, which also is a user-level-command. Therefore defining \GetLabelNameFromLabelNumber I couldn't use \NewExpandableDocumentCommand but had to resort to \newcommand. – Ulrich Diez Jul 13 '21 at 20:57
  • @JosephWright Generally: As soon as you wish to define a macro (be it expandable or not) which post-processes the tokens forming the result of another expandable macro you need to know about the amount of expansion-steps needed with that other expandable macro for obtaining that result. Why shouldn't a user wish to define her/his own macro, e.g., document-level-command, which post-processes the tokens forming the result of applying an expandable document-level-command? Wrapping things into \expanded{..} might as well expand the tokens forming that result although this might be undesired. – Ulrich Diez Jul 14 '21 at 16:51
  • 2
    @UlrichDiez If one macro depends on the number of expansion steps of another document level macro that would indicate very tight coupling which makes it very hard to modify anything about the involved macros. If the expandable macro generates values which should not get expanded further, it can just use \unexpanded around it to allow reuse without depending on implementation details. – Marcel Krüger Jul 14 '21 at 18:46
  • @UlrichDiez sorry but I don't think that an answer which involves patching internals of zref makes a good use case. If zref is missing a functionality the better step would be to ask for a proper interface. – Ulrike Fischer Jul 14 '21 at 18:56

0 Answers0