2

There exists some consensus on the fact that one shouldn't use $...$, \(...\) or \ensuremath inside of newcommand (or NewDocumentCommand, ...) definitions to wrap passed macro arguments that are to typeset in math mode:

\documentclass{article}

\begin{document}
\newcommand*{\somecommand}[1]{
    \(#1\)
}
\somecommand{Hello!}
\end{document}

Here, Hello! will be typeset as math, but the user/author of that macro might have never known what hit them. This leaves something to be desired and might be pretty surprising to the user. It should work, for example, like \somecommand{$Hello!$}, where the \newcommand definition omits entering math mode. The author should be in command and be aware of math and text modes.

Now, I have a command that is supposed to combine the macro argument input with more math, in this case a single equal sign:

\documentclass{article}

\begin{document}
\newcommand*{\somecommand}[1]{
    #1\(=\)
}
\somecommand{\(A\)}
\end{document}

But this destroys math spacing:

math spacing off

We effectively have \(A\)\(=\). That makes little sense. We can't define \somecommad to have \(#1=\): this works when #1 is just text, but not if it's math mode already (LaTeX Error: Bad math environment delimiter). That case would expand to \(\(A\) = \).

The question is: why does that not work?

The advantage of \( over $ is the clear possibility to match left and right delimiters. From the \ensuremath example linked above, it's obvious why the equivalent with $ doesn't work: $ $A$ = $ is legal syntax ($$A$ = $ is not), but utterly useless (A is in text mode). Why does nesting the LaTeX inline math delimiters \( not work? It could just ignore any inner \(...\) pair. Technically, there's no reason it should/could get confused (as opposed to $) (?).

To solve the above problem (have a macro that appends an equal sign/some more math to its input), there seem to be only two solutions, both bad:

  1. Have #1\(=\), destroying spacing and requiring manual intervention like \,.
  2. Have \(#1=\). Spacing works, but the input argument has to be in text mode. This is terrible for IDEs, linters etc. (will mark math mode characters like underscores as wrong since they'll be used outside of math mode etc.).

EDIT: Some more context to the question was requested. That context is pretty long; I tried to keep it to a usable minimum.

I am creating a template for an exam, based on the exam class. The formatting requirements are:

  1. Top of the page has the question for that page.
  2. Below is a box to pen in the answer (or part of it) to that question. The type of said answer can be flexible (usually a collection of required equations or a drawing).
  3. Below that large box, there might be an unknown number (0, 1, ...) of further boxes to partition students' answers further.
  4. All of this together should fill the current page vertically, automatically.
  5. Eventually, after the exam concluded, a solution sheet will be made available for future reference. The answers should, thanks to exam's facilities, be toggleable and filled in "automatically". This is the core issue that lead to the original question above (caller should enter arguments in math mode, but that "destroys" spacing aka we need manual intervention to get spacing right, but I wondered because nesting \(...\) should technically be possible arbitrarily)

Thanks to tcolorbox, the above can be achieved pretty nicely:

\documentclass[
    % answers,
]{exam}

\usepackage[
    raster,
    skins,
    % theorems,% `math` key
]{tcolorbox}

\usepackage{amsmath}

\usepackage{etoolbox}

\NewDocumentCommand{\answerblock}{mmmm}{%
    \tcbitem[
        raster multicolumn=3,% Full width
        title={Rearranged equation (symbolic)},
        equal height group=A,% All boxes in this group are forced to the same height
        % No matter the boxes natural height, conditionally leave some space for manual
        % answer entries:
        minimum for current equal height group=2cm,
    ]
        % Problem: the caller will pass the argument already set in math mode (set
        % inside `\( ... \)` -> DO NOT USE `$...$`). See also
        % https://tex.stackexchange.com/q/34830/120853 .
        % I asked about this here:
        % https://tex.stackexchange.com/q/593218/120853
        % We cannot put that argument *inside* another outer pair of `\(...\)`, it
        % will break.
        % This forces use to chain multiple math environments. This breaks the natural
        % spacing these have. Very sad and ugly, but we can hack a bit of space via
        % `\,`.
        % NOTE: DO NOT put text spaces here, they *will* show up in the result, since
        % we are in text mode in between the multiple math environments.
        #1\({}={}\)\ifprintanswers#2\fi%
    \tcbitem[
        raster multicolumn=2,
        title={Rearranged equation (numerical values)},
        equal height group=A,% All boxes in this group are forced to the same height
        % math,% from `theorems`; same issue with nested math ('Bad math environment delimiter')
    ]
        #1\({}={}\)\ifprintanswers#3\fi%
            \hfill{}\(=\)%
    \tcbitem[
        raster multicolumn=1,% 'Right' width
        title={Result},
        equal height group=A,% All boxes in this group are forced to the same height
        halign=center,
    ]
        \ifprintanswers#4\fi%
}

\NewDocumentEnvironment{solutionoranswerarea}{%
    d()% Force top box to this height, as a *percentage* of the *remaining* space
    O{% Top box descriptive text
        Collection of required equations%
    }
    +b% Environment body itself
}{%
    \begin{tcbitemize}[
        raster equal height=rows,% Core functionality to automatic sizing, see below
        raster every box/.style={
            valign=center,% Alignment for *content* inside of boxes
        },
    ]
    % The following is just an adaption from '15.6.2 Placing Spaces', see `tcolorbox`
    % manual page 312, version 4.42 (2020-10-09).
    % The idea is that we have two (the default) columns in an outer raster, each with
    % only a *single* row: 2 columns, 1 row.
    % However, the rows consist of `tcbitemize` environments, hence we can 'cheat' by
    % having two such inner environments in which we can put however many columns and
    % rows as we please.
    % The `raster equal height=rows` makes it so both *outer* rows are forced to the
    % same height through a couple iterations (=compilations); *within* the inner
    % `tcbitemize`, we can use a dynamic space macro to adjust a height to match said
    % outer height automatically (`space to` and `add to natural height`).
        \tcbitem[% First column (of default 2)
            blankest,% blankest style sets everything to empty/0pt
            space to=\dynamicfillspace,% (Forced height - natural height) -> save to macro
        ]
            \begin{tcbitemize}[% Nested itemization
                raster width=2\linewidth,% 2 columns, so double back to original value
                raster columns=3,% Full width
            ]
                \tcbitem[
                    raster multicolumn=3,% Full width
                    add to natural height=\dynamicfillspace,
                    title={#2},
                ]
                        #3
            \end{tcbitemize}
        \tcbitem[blankest]% Second column
            \begin{tcbitemize}[% Second nested itemization
                raster width=0pt,% 'Hide' this entirely
            ]
                \tcbitem[% Fixed height to which the first column will adapt (in height)
                    blankest,% blankest style sets everything to empty/0pt
                    height=\textheight-\pagetotal,% https://tex.stackexchange.com/a/207782/120853
                ]
            \end{tcbitemize}
}{
    \end{tcbitemize}
}
\begin{document}

\begin{questions}
    \question Please solve for \(d\). Write your answer below. Note the required fields.

        \begin{solutionoranswerarea}
            \begin{solution}
                \begin{align*}
                    a + b &= c \\
                    c &= 2d \\
                \end{align*}
            \end{solution}

            \answerblock{\(d\)}{\(\frac{a + b}{2}\)}{\(\frac{20 + 10}{2}\)}{15}
        \end{solutionoranswerarea}

    \clearpage
    \question Please solve for \(x\) \emph{and} \(y\).

        \begin{solutionoranswerarea}
            \begin{solution}
                \begin{align*}
                    z &= x \\
                    z &= y^{2} \\
                \end{align*}
            \end{solution}

            \answerblock{\(x\)}{z}{9}{9}
            \answerblock{\(y\)}{\(\sqrt{z}\)}{\(\sqrt{9}\)}{3}
        \end{solutionoranswerarea}

    \clearpage
    \question Please draw something.

        \begin{solutionoranswerarea}[Drawing]
            % Automatic solution requires extra work...
        \end{solutionoranswerarea}
\end{questions}
\end{document}

Prints (lualatex on TeXLive 2020; requires a couple runs to get automatic spacing right):

enter image description here

enter image description here

enter image description here

With the answers class option on, it prints (last page is identical, solution to that is not part of this "M"WE):

enter image description here

enter image description here

Things that don't work to remedy this:

  1. Using the tcolorbox theorems library with its math key. It leads to the same issue with Bad math environment delimiter when nesting
  2. We cannot do \(\answerblock{}{}{}{}\) or even \(\tcbitem ...\), aka wrapping the entire macro in math mode.

Question as above still stands:

the caller aka author should be in control of when to enter math mode for various reasons. Hence, we get \(...\) arguments to answerblock. Text input like \answerblock{...}{...}{\sqrt{9}}{...} is not acceptable (surprising, opaque behaviour and confuses syntax highlighting/linting). \(\answerblock{}{}{}{}\) would be okay but does not work, so we also cannot use \TextOrMath, sadly.

So, manual intervention for spacing is required, since we apparently cannot nest \(...\). Why is that so and is there a way around this? \(...\) should be nestable arbitrarily.

7
  • You could do \newcommand*{\somecommand}[1]{#1\({}=\)} but I'm not quite sure about the purpose... what about spacing after the equal sign? – campa Apr 16 at 18:06
  • Is LuaTeX possible? Then you could do \toks0={\(A\)\(=\)} and then postprocess the register with Lua to nuke the extra inline math tokens. – Henri Menke Apr 16 at 18:09
  • @campa Eventually, I also need a second argument #2 that gets appended to the equal sign. Space on that side should also work. This is for an exam template with empty to-be-filled fields, hence it's not a usual typographic use-case. @Henri Menke, this is already using lualatex actually. I've done the Lua part of lualatex a couple times before, it works well. Thanks for the heads-up. – Alex Povel Apr 16 at 18:22
  • Well, you could add an empty group also after the equal sign, so you'll always have \mathrel spacing. Of course you must be careful about other spaces passed with the arguments. – campa Apr 16 at 18:28
  • 1
    Third option: don't put math mode at all and force the user to do \(\somecommand{A}\). If the command is clearly intended for math only then that can be expected from the user, similar to other, predefined math commands. – Marijn Apr 16 at 20:47
2

Proposal:

  • enclose the entire \answerblock call in \( and \)
  • do not enclose the individual arguments in math delimiters
  • close math mode at the start of the \answerblock command
  • reopen math mode at the end of the \answerblock command
  • use math delimiters around the full math expressions within the \tcbitems

This way spacing is preserved without any extra syntax, and the editor/linter can also be tricked into thinking that the math is entered in math mode.

MWE (note the M):

\documentclass[answers]{exam}

\usepackage[raster]{tcolorbox}

% make syntax highlighters happy
\let\mathopen\(
\let\mathclose\)

\usepackage{xparse}

\NewDocumentCommand{\answerblock}{mmmm}{%
    \mathclose%
    \tcbitem[
        title={Rearranged equation (symbolic)},
    ]
        \(#1=\ifprintanswers#2\fi\)%
    \tcbitem[
        title={Rearranged equation (numerical values)},
    ]
        \(#1=\ifprintanswers#3\fi\)%
            \(\hfill{}=\)%
    \tcbitem[
        title={Result},
    ]
        \ifprintanswers#4\fi%
    \mathopen%
}
\begin{document}
\begin{tcbitemize}
% linter also happy
\(\answerblock{d}{\frac{a + b}{2}}{\frac{20 + 10}{2}}{15}\)
\end{tcbitemize}
\end{document}

Result:

enter image description here

0

Tex commands should almost aways be math or text, not both, note latex has \^ and \hat for text and math accents even though technically they could have been combined.

However if you really want to combine in to one, LaTeX provides \TextOrMath for that purpose.

enter image description here

\documentclass{article}

\begin{document}

\newcommand*{\somecommand}[1]{\TextOrMath{#1 = \ignorespaces}{#1={}}}

an = in text with word spacing \somecommand{ABC} foo

an = in math with \verb|\mathrel| spacing  $\somecommand{ABC}x$

\end{document}
8
  • The OP seems to want \somecommand{$ABC$} though. From the question: "It should work, for example, like \somecommand{$Hello!$}, where the \newcommand definition omits entering math mode". So if the argument is already math, stay in math, if the argument is text, do something else. – Marijn Apr 17 at 14:47
  • @Marijn hard to tell exactly what OP wants (I may edit this if the question is clarified) but if the current contest is text \somecommand{text} and \somecommand{$math$} both do something reasonable (= with text spacing) – David Carlisle Apr 17 at 16:54
  • I provided more context to the original question. It mainly boils down to nesting \(...\) and my lack of understanding why that is not possible. – Alex Povel Apr 19 at 7:50
  • @AlexPovel of course it would be possible to define \( to do nothing in math mode rather than give an error but it generates an error by design. Your main concern seems to be caller should enter arguments in math mode, but that "destroys" spacing but that is a misunderstanding on your part there is no reason to lose correct spacing here. – David Carlisle Apr 19 at 8:12
  • 1
    \( is essentially a user-level error catcher. It is the same as $ but it checks that you are starting and ending math correctly. That is something useful in hand written document code, but if you are writing a tested macro that may be called millions of times over its lifetime checking every time that the \( and \) match is rather pointless. It is same as I would usually use \def (or a non-checking expl3 version) rather than \newcommand in internal definitions as the checks that \newcommand does are useful at the top level but less so in internal code. – David Carlisle Apr 19 at 10:07

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.