0

Consider a macro that

  • declares some other document command, but by accepting their body as an own argument (simple indirection)
  • also adds the invocation of itself to some hook

as in

\NewDocumentCommand{ \DeclareStuff } { m }
{
   \DeclareDocumentCommand { \Select } { m m } { #1 }
   \AddToHook { hook } { \DeclareStuff { #1 } }
}

calling \DeclareStuff { #1 } will have the effect that now \Select{one}{two} expands to just one. However, the hook now contains the token list

\DeclareStuff { ##1 }

and I understand what happened here:

  • When expanding \DeclareStuff { #1 }, we replace the # with a double ##, and place the definition of \DeclareStuff into the input stream.
  • Thus, #1 will be expanded to ##1 when found in the macro body
  • when \DeclareDocumentCommand { \Select } { m m } searches for its macro body, it will thus first see #1, resulting in ##1 being placed in the input stream, which then expands to #1 again (eliminating the double #) as the argument received.
  • So this is equivalent to just declaring \DeclareDocumentCommand { \Select } { m m } { #1 } (essentially redefining \use_i:nn)

So far, this is what I want / expected, however

  • When processing the \AddToHook and encountering the #1, we will replace this by ##1 as before, but not expand this further, since apparently the hook does not expand this further (why?)
  • This results in the above definition

Actually, I would like to be able to execute this hook at a later time and thus restore the same definition again.

So my question is: What is the difference between the two expansions of the #1 encountered in the \DeclareStuff macro 'body'? What can I do so that executing the hook has the exact same effect as the \DeclareStuff macro that is currently being expanded, that is, how can I save 'this current 'call'' of the macro for later re-using?


Some context that is not acutally relevant for the question:

  • I want to do this as the hook actually has to write these informations ( in a slightly modified way) into the aux file at the end of the document so that in the next run of LaTeX, I can reuse these definitions.
  • The reason for this is that there is quite some calculation involved before the \DeclareStuff macro is called from somewhere else, and I can save these calculations on the second run by directly using \DeclareStuff.
  • The macros that do these calculations and ultimately call \DeclareStuff are of course redefined as use_none:.. in the second run of LaTeX, I set a flag in the aux file for this purpose

2 Answers 2

1

The doubled # is just an artifact of the way #in replacement texts are shown, if you run the hook you will see the effective definition is the same as the direct use.

This defines \Select as you describe, then defines it to be \relax then running the hook again defines it to have the original definition again.

\documentclass{article}

\begin{document}

\ExplSyntaxOn
\NewHook{hook}

\NewDocumentCommand{ \DeclareStuff } { m }
{
   \DeclareDocumentCommand { \Select } { m m } { #1 }
   \AddToHook { hook } { \DeclareStuff { #1 } }
}
\ExplSyntaxOff


\DeclareStuff{#1}

\ShowCommand\Select

\Select{one}{two}


\DeclareDocumentCommand { \Select } {}{zz}
\ShowCommand\Select

\UseHook{hook}


\ShowCommand\Select

\Select{one}{two}


\end{document}
0
1

Let's look at the thing:

\NewDocumentCommand{ \DeclareStuff } { m }
{
   \DeclareDocumentCommand { \Select } { m m } { #1 }
   \AddToHook { hook } { \DeclareStuff { #1 } }
}

If you do \DeclareStuff{<Definition text>}, you get:

\DeclareDocumentCommand { \Select } { m m } { <Definition text> }
\AddToHook { hook } { \DeclareStuff { <Definition text> } }

The reducing of consecutive hashes when expanding a macro whose definition-text contains them is not relevant here.

The doubling of hashes that end up inside a macro-definition via using \edef\macro{\unexpanded{...}} or via using \edef\macro{\the<token register>} (or via using \xdef\macro...) for compensating the circumstance that hashes get reduced at the time of expanding the macro in question is also not relevant here.

The doubling of hashes when writing to external text-file or to screen ( e.g., via \show or via \immediate\write-1{...} or via \message{...}) or when doing \detokenize or \scantokens also is not relevant here.

The crucial point is:

Your \DeclareStuff adds its own invocation to a hook.

Thus if the hook gets carried out, \DeclareStuff gets carried out, resulting in adding its own invocation to the hook again.

This way with each invocation of the hook the amount of \DeclareStuff stored in the hook gets doubled.

You can avoid this by introducing a boolean indicating whether \DeclareStuff shall add things to the hook and inside the hook adding a directive for setting the boolean so that things won't be added to the hook. If you do this you must ensure that this directive is always the first thing that gets carried out by the hook.

\documentclass{article}

\ExplSyntaxOn
\bool_new:N \g__mymodule_AddToMyHook_bool
\NewHook{MyHook}
\cs_new:Npn \AddToMyHooktrue {\bool_gset_true:N \g__mymodule_AddToMyHook_bool}
\cs_new:Npn \AddToMyHookfalse {\bool_gset_false:N \g__mymodule_AddToMyHook_bool}
\AddToHook{MyHook}{\AddToMyHookfalse}
\AddToMyHooktrue

\NewDocumentCommand{ \DeclareStuff } { m }
{
   \DeclareDocumentCommand { \Select } { m m } { #1 }
   \bool_if:NT \g__mymodule_AddToMyHook_bool {
     \AddToHook { MyHook } { \DeclareStuff { #1 } }
   }
}
\ExplSyntaxOff

\begin{document}

% This defines \Select and appends the definition to the hook:
\AddToMyHooktrue
\DeclareStuff {Argument 1 is: #1. Argument 2 is: #2.}
\expandafter\show\csname Select code\endcsname

% This defines \Select but does not append the definition to the hook:
\AddToMyHookfalse
\DeclareStuff {ArGuMeNt 1 is: #1. ArGuMeNt 2 is: #2.}
\expandafter\show\csname Select code\endcsname


% This redefines \Select  as often as \DeclareStuff was invoked while
% \AddToHookstrue was in effect. In the end the last \DeclareStuff 
% invoked while \AddToHookstrue was in effect makes the definition
% of \select:
\UseHook{MyHook}
\expandafter\show\csname Select code\endcsname

\end{document}

Console output is:

$ pdflatex-dev test.tex
This is pdfTeX, Version 3.14159265-2.6-1.40.21 (TeX Live 2020) (preloaded format=pdflatex-dev)
 restricted \write18 enabled.
entering extended mode
(./test.tex
LaTeX2e <2021-05-01> pre-release-1 (develop 2021-2-27 branch)
L3 programming layer <2021-02-18>
(/usr/local/texlive/2020/texmf-dist/tex/latex-dev/base/article.cls
Document Class: article 2020/11/23 v1.4m Standard LaTeX document class
(/usr/local/texlive/2020/texmf-dist/tex/latex-dev/base/size10.clo))
(/usr/local/texlive/2020/texmf-dist/tex/latex/l3backend/l3backend-pdftex.def)
(./test.aux)
> \Select code=\protected\long macro:
#1#2->Argument 1 is: #1. Argument 2 is: #2..
<recently read> \Select code 
                             
l.60 ...andafter\show\csname Select code\endcsname
                                                  
? 
> \Select code=\protected\long macro:
#1#2->ArGuMeNt 1 is: #1. ArGuMeNt 2 is: #2..
<recently read> \Select code 
                             
l.65 ...andafter\show\csname Select code\endcsname
                                                  
? 
> \Select code=\protected\long macro:
#1#2->Argument 1 is: #1. Argument 2 is: #2..
<recently read> \Select code 
                             
l.73 ...andafter\show\csname Select code\endcsname
                                                  
? 
(./test.aux) )
No pages of output.
Transcript written on test.log.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .