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.

  • 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 '21 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 '21 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 '21 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 '21 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 '21 at 20:47
  • Your question is very long but if this is for math why not the simple #1= ? tex commands should almost always be math-only or text-only. Note if your command is for use in text you need to remove all the white space in the defintion. – David Carlisle Apr 16 '21 at 21:42
  • It is not clear what output you want as you say #1\(=\) destroys spacing but \(#1=\) spacing works, but in both these cases you get a = with mathord class and no space, to get a relational = you would need \(#1={}\) or #1\({}={}\) (I would use $ not \( in a definition) – David Carlisle Apr 16 '21 at 21:53

2 Answers2

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

Marijn
  • 37,699
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}

David Carlisle
  • 757,742
  • 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 '21 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 '21 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 '21 at 07: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 '21 at 08:12
  • @AlexPovel specifically it is your use of \(=\) (I would always use $ rather than \( in macros, but that is not the issue here) \(=\) is equivalent to = and will make an = without math releation spacing. It is simply that coding error that is giving you spacing issues, not the definition of \( – David Carlisle Apr 19 '21 at 08:16
  • @DavidCarlisle Okay, I don't understand. In the definition body of \answerblock, for testing purposes I now changed all \(...\) to $...$. chktex now yells at me to reverse this. (Why would you prefer $ there even if this is LaTeX code?) Of course this didn't solve the issue; how can I actually solve the question of the OP? Leaving out all math mode (\( or $ both) is also not an option, see the revised question ($\answerblock{}{}{}{}$ is not an option). I originally used \( because that is at least theoretically nestable -- $ is by definition not nestable. – Alex Povel Apr 19 '21 at 09:30
  • 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 '21 at 10:07
  • @DavidCarlisle Thank you very much, that was a helpful explanation. – Alex Povel Apr 20 '21 at 12:11