22
\documentclass[pstricks,border=12pt]{standalone}
\usepackage{pst-node}
\begin{document}
\begin{pspicture}[showgrid](-5,-5)(5,5)
    \pnodes{A}(-2,3)(3,4)(2,-1)(-2,-4)(-4,0)
    \def\points{(0,0)}
    \foreach \x in {0,1,...,4}{%
            \pscircle*[linecolor=blue](A\x){3pt}%
            \xdef\points{\points+.2(A\x)}%
    }
    %The first line below causes errors.
    %\expandafter\nodexn{\points}{B}
    %\pscircle*[linecolor=red](B){3pt}
\end{pspicture}
\end{document}

How do I have to invoke \expandafter for a macro with multiple arguments?

7
  • 3
    Does \expandafter\nodexn\expandafter{\points}{B} work? You're just expanding the { token (which stays {). The LaTeX kernel also defines \@expandtwoargs which would \edefs the first two arguments of a macro: \@expandtwoargs\nodexn{\points}{B}. Sep 17, 2013 at 13:42
  • @Qrrbrbirlbel: Yes. It works. Thank you! Now, what do I have to do? Delete this question or wait for your answer and then accept it? Sep 17, 2013 at 13:43
  • 1
    Don't forget this trick if the argument you want to expand is at the end of a long list. Just \def the list up to that point, and expandafter the \def and following {: \documentclass{article} \usepackage[T1]{fontenc} \newcommand\manyargs[4]{#1.#2.#3.\detokenize{#4}} \def\expandthis{EXP} \begin{document} \manyargs{a}{b}{c}{\expandthis} \def\tmp{\manyargs{a}{b}{c}} \expandafter\tmp\expandafter{\expandthis} \end{document} I learned that from David Carlisle. Sep 17, 2013 at 13:51
  • @StevenB.Segletes I hope you don't mind that I have included your (or David's) trick in my answer. I've learned it by reading many sources where similar expansion tricks are often used. Sep 17, 2013 at 14:17
  • 1
    with the pst-node.tex from texnik.dante.de/tex/generic/pst-node you can simply write \nodexn{\points}{B} without any \expandafter
    – user2478
    Sep 17, 2013 at 18:23

4 Answers 4

17

You need to do

\expandafter\nodexn\expandafter{\points}{B}

you need to "jump over" the { too otherwise

\expandafter\nodexn{\points}{B}

just expands { once (to {) before \nodexn gets expanded.

The LaTeX kernel defines a helpful macro \@expandtwoargs as

\def\@expandtwoargs#1#2#3{%
  \edef\reserved@a{\noexpand#1{#2}{#3}}\reserved@a}

which \edefs the first two arguments of a macro before "executing" it. Obviously, you would want to do something like

\makeatletter
\let\expandtwoargs\@expandtwoargs
\makeatother

to be able to use it comfortably. (And of course, this doesn't work for arguments that are not safely fully expandable.)

If you want to expand more than two arguments you can use a temporary macro which contains only the arguments:

\edef\tmp{{\argumentA}{\argumentB}{\argumentC}{\argumentD}}

which then only needs to be expanded once:

\expandafter\myMacro\tmp % \tmp already contains the braces

From the same trick box, but quite the opposite problem, namely to expand an argument at the end of a macro with many arguments, can be solved by stuffing everything up to the argument in a temporary macro which than can be "jumped over" very easily:

\def\B{B}
\def\tmp{\myMacro{argument A}{argument B}}
\expandafter\tmp\expandafter{\argumentC}

Otherwise you would need to do

\expandafter\myMacro\expandafter{\expandafter a\expandafter r\expandafter g …
4
  • One more question based on my answer here (locate the first code snippet at the top): Why don't I need \expandafter when invoking \pstHomO even though I use \Points as an argument? Sep 17, 2013 at 14:32
  • Does that mean you can just drop the braces, i.e. that \expandafter\nodexn\points{B} would also work?
    – Jonathan
    Sep 17, 2013 at 19:11
  • 1
    @Jonathan No, as \nodexn would than only grab the first two token of the expansion of \points (( and 0) as its arguments instead of the expansion of \points as the first and B as the second argument. Sep 18, 2013 at 11:17
  • @PGFTricks Unfortunately, I don’t know the exact syntax of \pstHomO (or what it actually does), but \Points only contains a list of things without =. Observe what happens if you do \edef\Points{\Points,A2=1} directly before \pstHomO and compare with \pstHomO[…]{O}{\Points,A2=1} (even though the =1 is thrown away anway). The = is not properly parsed in the first case but in the second. (You may fix this with the following redefinition: \def\@List#1{\xdef\@NewList{none}\expandafter\@List@i#1,\@nil\ignorespaces}.) Sep 18, 2013 at 11:31
14

This is a comment on the more general topic raised in Qrrbrbirlbel's answer of selectively expanding one argument of a macro before executing the macro itself. I will take here "expanding" as meaning expanding once, and it is not assumed that the arguments are only single tokens (the expansion will be applied only to the first token of the argument; this could provoke further expansions among this argument of course). My comment is that it is possible to do this expandably.

Nota Bene: a more powerful such "expandable expander" has been added in an edit, below. It also expands only once the (first token of the) targeted argument but this can be promoted to a "full expansion". (see code)

We need these utilities:

\def\expandArg #1#2{\expandafter\expandArgaux\expandafter{#2}{#1}}
\def\expandArgaux #1#2{#2{#1}}

Then imagine I need to have the 3rd of the 5 arguments to \mymacro be expanded once before the execution of \mymacro:

\expandArg {\mymacro {Not me}{Not me}}{Expand Me}{Not me}{Not me} 

does the trick. The difference with the trick mentioned in Qrrbrbirlbel's answer and in comments is that this is expandable in the sense of not needing a \def.

To illustrate:

\def\expandArg #1#2{\expandafter\expandArgaux\expandafter{#2}{#1}}
\def\expandArgaux #1#2{#2{#1}}

\tt
\def\mymacro #1#2#3#4#5{\meaning #3}
\def\x {\y}
\def\y {z}
\mymacro {1}{2}{\x}{4}{5}
\par
\expandArg {\mymacro {1}{2}}{\x}{4}{5}
\bye

Output:

expansion-trick

I mentioned in a comment that it was also possible to have the more user-friendly syntax:

\ExpandNth {5}\mymacro{A}{B}{C}{D}{E}{F}{G}

without extra braces, thus, to expandably expand the fifth argument only, prior to the execution of \mymacro.

Here is one way. It acts correctly if given an N zero or less, but will create errors if N exceeds the actual number of arguments. It is also crucial that no spaces are left in-between the arguments of the macro \mymacro to which the expandable expander is applied.

\catcode`\_ 11

\def\ExpandNth #1%
{%
    \romannumeral0\ifnum #1>0
      \expandafter\ExpandNth_a
    \else
      \expandafter\ExpandNth_none
    \fi {#1}%
}
\def\ExpandNth_none #1{ }
\def\ExpandNth_a #1#2{\ExpandNth_b {#1}{#2}}
\def\ExpandNth_b #1%
{%
    \ifnum #1>1
      \expandafter\ExpandNth_c
    \else
      \expandafter\ExpandNth_w
    \fi {#1}%
}
\def\ExpandNth_c #1#2#3%
{%
    \expandafter\ExpandNth_b\expandafter
        {\the\numexpr #1-1}{#2{#3}}%
}%
\def\ExpandNth_w #1#2#3%
{%
    \expandafter\ExpandNth_z\expandafter {#3}{#2}%
}%
% replace #3 above by \romannumeral-`0#3 for "full" expansion

\def\ExpandNth_z #1#2{ #2{#1}}%

\catcode`\_ 8       

\tt

\def\mymacro #1#2#3#4#5{\meaning #1\par
                        \meaning #2\par
                        \meaning #3\par
                        \meaning #4\par
                        \meaning #5\par}
\def\a{\A} \def\A{A}
\def\b{\B} \def\B{B}
\def\c{\C} \def\C{C}
\def\d{\D} \def\D{D}
\def\e{\E} \def\E{E}

\hsize 12cm

Original \string\mymacro:\par
\mymacro \a\b\c\d\e

\medskip\hrule\medskip

With prior (once) expansion of the first argument:\par
\ExpandNth {1}\mymacro \a\b\c\d\e

\medskip\hrule\medskip

With prior (once) expansion of the second argument:\par
\ExpandNth {2}\mymacro \a\b\c\d\e

\medskip\hrule\medskip

With prior (once) expansion of the third argument:\par
\ExpandNth {3}\mymacro \a\b\c\d\e

\medskip\hrule\medskip

With prior (once) expansion of the fourth argument:\par
\ExpandNth {4}\mymacro \a\b\c\d\e

\medskip\hrule\medskip

With prior (once) expansion of the fifth argument:\par
\ExpandNth {5}\mymacro \a\b\c\d\e

\bye

Output:

selective expansion

4
  • The user must be careful not to leave a space after {\x} in the \expandArg application above. We could define \expandArg with 3 arguments, and then the input would be \expandArg {\mymacro ...}{Expand me}{all the rest} and spaces would be allowed either before or after the {Expand me}.
    – user4686
    Sep 17, 2013 at 15:09
  • It would also be possible to construct a \ExpandNth {n} such that \ExpandNth {5}\mymacro{A}{B}{C}{D}{E}{F}{G} expands the fifth argument, with no need for the user to put extra braces,; still working "expandably". But this is more complicated.
    – user4686
    Sep 17, 2013 at 15:13
  • Code has been edited to implement the macro mentioned in my previous comment.
    – user4686
    Sep 17, 2013 at 15:51
  • When I say that for N>#args errors are created this is not systematic: the effect is to expand the Nth token or braced material found and to reinject it within braces; and all those tokens in-between the last argument of the macro and the Nth spot will end up (each) in braces (or left unchanged if it was already {..} material).
    – user4686
    Sep 17, 2013 at 17:30
4

For this particular application you can simply do

\newcommand{\nodexnX}[1]{\expandafter\nodexn\expandafter{#1}}

and call \nodexnX{\points}{B}

Or you can go further and redefine \nodexn so that it fully expands its first argument

\let\PSTRICKSnodexn\nodexn
\renewcommand{\nodexn}[1]{%
  \begingroup\edef\x{\endgroup
  \noexpand\PSTRICKSnodexn{#1}}\x
}

The LaTeX3 kernel has \exp_args:Nx that's implicitly added with \cs_generate_variant:Nn:

\usepackage{expl3}
\ExplSyntaxOn
\cs_set_eq:NN \pstricks_nodexn:nn \nodexn
\cs_generate_variant:Nn \pstricks_nodexn:nn { x }
\cs_set_eq:NN \nodexn \pstricks_nodexn:xn
\ExplSyntaxOff

If you need to fully expand also the second argument, you can do

\cs_set_eq:NN \pstricks_nodexn:nn \nodexn
\cs_generate_variant:Nn \pstricks_nodexn:nn { xx }
\cs_set_eq:NN \nodexn \pstricks_nodexn:xx

In both case you have effectively redefined \nodexn to expand its argument(s) before the original macro is called.


Addition 2022

In the next LaTeX kernel, it will be available \ExpandArgs and

\ExpandArgs{x}\nodexn{\points}{B}

will do the trick. This is an interface to \exp_args:Nx, meaning that the next token is jumped over and the contents of the following braced group will be fully expanded.

You you want to fully expand also the second argument?

\ExpandArgs{xx}\nodexn{\points}{\foo}

will do.

So long as \exp_args:N<characters> exists, one can use \ExpandArgs{<characters>}. The characters are

  • N to denote “jump over a token”
  • n to denote “jump over a braced group”
  • x to fully expand the contents of a braced group using \edef
  • e to fully expand the contents of a braced group using \expanded
  • f to recursively the contents of a braced group until finding an unexpandable token
  • o to expand once the first token inside a braced group

If a needed particular combination does not exist, there is no “official“ user interface to create it, but one can do

\ExplSyntaxOn
\exp_args_generate:n { <characters>, ... }
\ExplSyntaxOff

Many combinations can be generated at once, separating the strings of characters by a comma.

4
  • Does "braced group" also work for [arguments] in square brackets?
    – Eric
    Jan 3 at 13:07
  • @Eric No, this isn’t possible. Usually optional arguments ate grabbed and then processed at a lower level as n-type arguments.
    – egreg
    Jan 3 at 13:35
  • The context here is trying to use \subfix (from subfiles) inside \newmintinline (from minted) as \newmintinline[mylang]{\subfix{mylang.py}:MyLexer}, and needing to ensure that subfix is immediately expanded. So I don't need to modify the expansion of the [] argument, but I do need to skip over it. Is this possible with ExpandArgs?
    – Eric
    Jan 3 at 13:39
  • @Eric You can use \expanded{\noexpand\newmintinline[mylang]{\subfix{mylang.py}:MyLexer}}
    – egreg
    Jan 3 at 20:40
0

With functional package, tricky argument expansion can be replaced with intuitive function composition, which is similar to other programmming languages such as Lua:

\documentclass[pstricks,border=12pt]{standalone}

\usepackage{pst-node}

\usepackage{functional}
\Functional{scoping=true} % make every function become a group

\begin{document}

\IgnoreSpacesOn
\PrgNewFunction \NodeXn { m m } {
  \nodexn {#1} {#2}
}
\IgnoreSpacesOff

\begin{pspicture}[showgrid](-5,-5)(5,5)
    \pnodes{A}(-2,3)(3,4)(2,-1)(-2,-4)(-4,0)
    \def\points{(0,0)}
    \foreach \x in {0,1,...,4}{%
            \pscircle*[linecolor=blue](A\x){3pt}%
            \xdef\points{\points+.2(A\x)}%
    }
    \NodeXn{\Value\points}{B}
    \pscircle*[linecolor=red](B){3pt}
\end{pspicture}

\end{document}

enter image description here

You must log in to answer this question.

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