0

I have a use case where I need to do some repeated mathematical computation within my document (as in, computing radii for many different circles in a TikZ loop). And even after many years writing documents in LaTeX, I just can't figure out the best or right way to pass macros to other macros as arguments. Below I've included a very simple example of the kind of thing I am talking about.

I'm pretty sure this has to do with macro expansion, how macros are not the same as functions, and how TeX inserts a space after the control codes \MyAdd. And I've found a few detailed explanations about what is going on under the hood. What I have not been able to find is a solution. What is the simplest, yet most correct way to take the output of one macro, potentially store it in a "variable," and pass either the full macro or the variable to another macro for further processing?

Thanks.

\documentclass{article}
\usepackage{xparse}
\usepackage{xfp}

\NewDocumentCommand{\MyAdd}{m m}{
  \fpeval{#1 + #2}
}
\NewDocumentCommand{\MyMult}{m m}{
  \fpeval{#1 * #2}
}
\NewDocumentCommand{\MyArgs}{m m}{
%% Inspect arguments as passed
  \immediate\write20{Arg 1:  #1}
  \immediate\write20{Arg 2:  #2}
}

\setlength{\parindent}{0pt}
\begin{document}

Correct: \MyAdd{1}{3}\\
Correct: \MyMult{4}{7}\\

%% Store results as macros themselves
\def\factA{\MyAdd{1}{3}}
\def\factB{\MyAdd{4}{7}}

Correct: \factA \\

% The next two lines generate the following errors:
%  - Erroneous variable \fpeval used!
%  - Missing * inserted.
%  - Invalid operation fp_to_decimal(nan)

% \MyMult{\factA}{\factB}\\ %error
% \MyMult{\MyAdd{1}{3}}{\MyAdd{4}{7}}\\ %error

\MyArgs{\factA}{\factB}
%% Writes the following to the log.  Note the space after \MyAdd
% Arg 1: \MyAdd {1}{3}
% Arg 2: \MyAdd {4}{7}

\end{document}
0

The space after \MyAdd in the output of \MyArgs is completely standard. Any control word that sneaks in \write operation is always followed by a space.

Why? Suppose you have \write20{\relax and \relax}. By rule, there is no space after the first \relax when tokens are formed. If TeX didn't add a space after control words, the output would become

\relaxand \relax

which is a problem, isn't it?

You should worry of several other spaces you add in the code. Your first definition is completely equivalent to

\NewDocumentCommand{\MyAdd}{m m}{ \fpeval{#1 + #2} }

because an endline is converted to a space. Spaces between argument specifiers in \NewDocumentCommand are ignored. Not ignored are spaces in the replacement text. However, if you write the code as you did but after \ExplSyntaxOn, endline and spaces would be ignored.

The main problem of your code is another, though.

You should use \NewExpandableDocumentCommand for your \MyAdd and \MyMult commands. And \edef if you want to store the result in a control sequence.

\documentclass{article}
\usepackage{xparse}
\usepackage{xfp}

\NewExpandableDocumentCommand{\MyAdd}{m m}{%
  \fpeval{#1 + #2}%
}
\NewExpandableDocumentCommand{\MyMult}{m m}{%
  \fpeval{#1 * #2}%
}
\NewDocumentCommand{\MyArgs}{m m}{%
%% Inspect arguments as passed
  \immediate\write20{Arg 1:  #1}%
  \immediate\write20{Arg 2:  #2}%
}

\setlength{\parindent}{0pt}

\begin{document}

Correct: \MyAdd{1}{3}\\
Correct: \MyMult{4}{7}

%% Store results as macros themselves
\edef\factA{\MyAdd{1}{3}}
\edef\factB{\MyAdd{4}{7}}

Correct: \factA

% The next two lines generate the following errors:
%  - Erroneous variable \fpeval used!
%  - Missing * inserted.
%  - Invalid operation fp_to_decimal(nan)

\MyMult{\factA}{\factB}
\MyMult{\MyAdd{1}{3}}{\MyAdd{4}{7}}

\MyArgs{\factA}{\factB}
%% Writes the following to the log.  Note the space after \MyAdd
% Arg 1: \MyAdd {1}{3}
% Arg 2: \MyAdd {4}{7}

\end{document}

If you define \MyAdd and \MyMult with \NewDocumentCommand, they will be \protected, so they don't expand inside \edef and so this expanded definition would do nothing at all.

1
  • Thanks, this was quite helpful. A follow-up question: Is it correct to say that, in general, bracketing the xparse command definitions with \ExplSyntaxOn and \ExplSyntaxOff is more "forgiving" with spaces, and that I should make that standard practice? Are there any downsides? Second follow-up question: Once I do add the ExplSyntax macros, whether I use def or edef, or NewExpandableDocument Command or not, does not seem to make a difference. So I am still a little confused about which to use where.
    – mbsmu
    May 17 at 16:43
1

As you indicated yourself, there are some spurious spaces. I removed them by adding %.

\documentclass{article}
\usepackage{xparse}
\usepackage{xfp}

\NewDocumentCommand{\MyAdd}{m m}{%
  \fpeval{#1 + #2}%
}
\NewDocumentCommand{\MyMult}{m m}{%
  \fpeval{#1 * #2}%
}
\NewDocumentCommand{\MyArgs}{m m}{%
%% Inspect arguments as passed
  \typeout{Arg 1:  #1}%
  \typeout{Arg 2:  #2}%
}

\setlength{\parindent}{0pt}
\begin{document}

Correct: \MyAdd{1}{3}\par
Correct: \MyMult{4}{7}\par

%% Store results as macros themselves
\def\factA{\MyAdd{1}{3}}
\def\factB{\MyAdd{4}{7}}
Correct: \factA \par
Correct: \factB \par

% The next two lines generate the following errors:
%  - Erroneous variable \fpeval used!
%  - Missing * inserted.
%  - Invalid operation fp_to_decimal(nan)

\MyMult{\factA}{\factB}\par %error
\MyMult{\MyAdd{1}{3}}{\MyAdd{4}{7}}\par %error

\MyArgs{\factA}{\factB}
%% Writes the following to the log.  Note the space after \MyAdd
% Arg 1: \MyAdd {1}{3}
% Arg 2: \MyAdd {4}{7}

\end{document}

enter image description here

Note, however, that instead of

\def\factA{\MyAdd{1}{3}}
\def\factB{\MyAdd{4}{7}}

you probably want

\edef\factA{\MyAdd{1}{3}}
\edef\factB{\MyAdd{4}{7}}

because further nesting will cause troubles if you keep using \defs.

4
  • You should try the code you propose: \edef and \def would make no difference at all. I'm sure you can guess why even without reading my answer.
    – egreg
    May 17 at 8:16
  • Obviously you did not read my answer. Besides, you recommend using \edef in your writeup.
    – user242026
    May 17 at 8:31
  • I read your answer carefully. You didn't notice the important change I made to the definition of \MyAdd and \MyMult. You obviously didn't try \edef with your unchanged code, did you? You'll have a surprise if you do.
    – egreg
    May 17 at 12:15
  • That did solve the problem. Much appreciated.
    – mbsmu
    May 17 at 16:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.