Take the 2-minute tour ×
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

In trying to fix a problem with a nested macro repeatedly calling itself, I tried expanding the inner call before passing it as an argument to the outer one. This failed miserably because the macro was defined using \newcommand with optional arguments. So my questions:

Why isn't something defined as \newcommand{\test}[2][]{} expandable?

Is there an expandable alternative?

Here's a simple example:

\documentclass{article}

\newcommand{\test}[2]{got #1 and #2}
\newcommand{\testopt}[2][nothing]{got #1 and #2}

\begin{document}

\edef\result{\test{one}{two}}
\result

\edef\result{\testopt{two}}
\result

\end{document}
share|improve this question
add comment

4 Answers

up vote 11 down vote accepted

As egreg explains, a fully robust check for optional arguments must use \futurelet and cannot be expandable (in fact, \newcommand does not make a fully correct check: try \let\lbrack[\testopt\lbrack... with your definition of \testopt, but it is possible to fix it).

Expandably, there are two possibilities to look ahead: either grab an undelimited argument, or grab a delimited argument. The first one removes braces: \testopt{[}a]{b} will be misrecognized as identical to \testopt[a]{b}. For the second method we need to decide until what tokens we should grab, and those tokens must be present. One possibility, since the second argument of \testopt is mandatory, is to force the user to put it in braces, and grab until the first open brace.

  • Undelimited argument: we need a test for emptyness (\detokenize sets all the catcodes to 12 or 10, different from the catcode of $). Then define \testopt to grab one argument, and compare it with [: if it does not contain [, then there was no optional argument, and #1 is the mandatory argument. Otherwise, test if it is alone in #1 (slightly dirty code to cater for the case \testopt{{}[}), in which case we consider that there was an optional argument.

    \makeatletter
    \newcommand{\@ifstrempty}[1]{%
      \ifcat$\detokenize{#1}$%
        \expandafter\@firstoftwo
      \else
        \expandafter\@secondoftwo
      \fi}
    \newcommand{\testopt@do}[2]{got #1 and #2}
    \newcommand{\testopt}[1]{%
      \expandafter\@ifstrempty
      \expandafter{\testopt@i#1[}
        {\testopt@do{nothing}{#1}}
        {\testopt@ii{#1}}}
    \long\def\testopt@i#1[{}
    \newcommand{\testopt@ii}[1]{%
      \expandafter\expandafter\expandafter\@ifstrempty
      \expandafter\expandafter\expandafter
        {\expandafter\testopt@iii\detokenize{#1}}
        {\testopt@iv#1}
        {\testopt@do{nothing}{#1}}
    \long\def\testopt@iii#1[{#1}
    \long\def\testopt@iv[#1]{\testopt@do{#1}}
    
  • Delimited argument: grab until a left brace, test if that starts with a [.

    \long\def\testopt#1#{%
      \@ifstrempty{#1}{\testopt@do{nothing}}
        {%
          \expandafter\@ifstrempty\expandafter{\testopt@i#1[}
            {\testopt@do{nothing}#1}
            {\testopt@iv#1}%
        }%
    }
    
share|improve this answer
add comment

A command with an optional argument can't be "completely expandable", because it needs to look at the character that follows in order to see whether it's a square bracket or not. Such a look ahead has to be done with \futurelet and requires an assignment which is performed only "in TeX's stomach".

There are some clever tricks that in particular situations allow for completely expandable macros with optional arguments, but with many limitations.

In LaTeX 2.09 all macros with optional argument were fragile; LaTeX2e has changed the situation, making those defined through \newcommand robust. In any case, using them in an \edef is hopeless.

share|improve this answer
3  
I disagree with your second paragraph: completely expandable means that it works in an \edef (otherwise it is not completely expandable). –  Bruno Le Floch Jun 24 '11 at 10:41
    
@Bruno: Fixed, I hope. –  egreg Jun 24 '11 at 11:54
    
indeed. –  Bruno Le Floch Jun 24 '11 at 13:23
    
@egreg LaTeX2e macros with optional arguments are only robust if declared with \DeclareRobustCommand, I think. I understand from David and Frank that there was simply not enough space to do that for everything defined with \newcommand. –  Joseph Wright Apr 4 '12 at 21:52
    
@JosephWright Also macros with an optional argument (via \newcommand) are, just in a different way. Not those with only mandatory arguments, of course. Look at \show\x after \newcommand{\x}[1][]{#1}: in a \protected@edef it becomes \x. –  egreg Apr 4 '12 at 21:59
add comment

The normal optional argument provided by LaTeX uses \futurelet (i.e. over \@ifnextchar) to look ahead if the next token is [. This is an assignment which is not expandable, but has to be executed.

The etextools package provides \FE@testopt as a fully expandable (FE) version of the core LaTeX macro \@testopt with is IIRC used by macros with optional arguments defined by \newcommand. This however has the limitation that the macro must have at least one mandatory argument and is not called with {[}.

The general usage is:

\def\MacroWithOption#1{\FE@testopt{#1}{\MacroHasOption}{default value}}

See section 6 Fully expandable macros with options and modifiers of the package manual for all details. It uses eTeX's \detokenize to test #1 safely.

Your example could be written like this using the package:

\documentclass{article}
\usepackage{etextools}

\makeatletter
\newcommand{\test}[2]{got #1 and #2}
\def\testopt#1{\FE@testopt{#1}{\test@opt}{nothing}}
\def\test@opt[#1]#2{got #1 and #2}
\makeatother


\begin{document}
\ttfamily
\edef\result{\test{one}{two}}
\meaning\result

\edef\result{\testopt{two}}
\meaning\result

\end{document}

This gives:

macro:->got one and two
macro:->got nothing and two

I myself implemented a similar fully expandable optional argument test macro for my newer filemod package. However, it is only intended to accept simple optional arguments (a single number) and might break with more complex input.

share|improve this answer
add comment

The answers to LaTeX command with optional arguments refer to the new xparse package (from the LaTeX3 project). It is

intended as a replacement for the LaTeX 2ε \newcommand macro

and offers a \DeclareExpandableDocumentCommand which promises to work around the issues presented in the other answers.

The code

\DeclareExpandableDocumentCommand\foo{ O{} m }{ ... }

creates an expandable variant of

\newcommand\foo[2][]{ ... }

. However,

this imposes a number of restrictions on the nature of the arguments accepted by a function, and the code it implements.

Also, it is not in the list of the "stable" commands and hence must be

regarded as "experimental"

and

may change or disappear.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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