5

I would like to define a macro (macrob) to alter the behaviour of another macro (macroa), depending on optional arguments.

I have done it before using the likes of IfNoValueTf or \IfBooleanTF, but this case is a bit more tricky, as I would like to be able to pass optional key-value pairs.

The signature would be {sO{}mO{}} where the keys are the second argument.

Basically:

  1. \macrob*{expr} or \macrob*{expr}[opt]should be equivalent to \macroa*{\expr}{opt|default}, ignoring the optional arguments of \macrob.
  2. \macrob[key1=val1]{expr}[opt] should be equivalent to \macroa[val1]{expr}{opt}, ignoring the second key.
  3. \macrob[key2=true]{expr}[opt] should be equivalent to \macroa{expr}{opt}.
  4. \macrob[key2=true]{expr} should be equivalent to \macroa{expr}{} (as per the O{}).
  5. \macrob{expr}[opt] should be equivalent to \macroa{expr}{opt}.
  6. \macrob{expr} should be equivalent to expr.

Without key2, I would simply go with

\NewDocumentCommand\macrob{somO{}}{%
  \IfBooleanTF{#1}{\macroa*{#3}{#4}}{%
    \IfNoValueTF{#2}{\macroa[#2]{#3}{#4}}{\macroa{#3}{#4}}}

but I have no idea how to set a default boolean behaviour for a key (\bool_set_false:N?), or whether I can mix these logic constructs with expl3 syntax.

If it can be of any help, a Python mock-up would look like

def macroa(arg1, arg2, star=None, opt=None):
    # do things with arg1 and arg2, maybe star or opt


def macrob(man_arg, star=None, opt_arg=None, key1=None, key2=False):
    if star:
        return macroa(man_arg, opt_arg, True)  # case 1
    else:
        if key1:
            return macroa(man_arg, opt_arg, opt=key1)  # case 2
        else:
            if key2 or opt_arg:  
                return macroa(man_arg, opt_arg)  # case 3, 4, 5
            else:
                return man_arg  # case 6

EDIT: I changed the target signature from {sO{}mO{}} to {sO{}mO{}} (star, optional key-value pairs, mandatory, optional with default). The o in {somO{}} was a mistake and should have been O{} from the start.

The signature of \macroa is {somm}. Indeed, the case 1 should have read \macroa*{\expr}{opt} (fixed) and not \macroa*{\expr}[opt].



Apologies for my question being far from clear.

Here is a more detailed version of the problem.

The goal is to be able to typeset functions with an argument in parentheses, with the option to adjust the size of the delimiters without resorting to \left(\right), which tend to insert some additional space before the opening parenthesis. I use \DeclarePairedDelimiterXPP from mathtools for this. But, there are times when I just want to print the symbol associated to the function, without an argument. Or, times when I want to print it with a placeholder between the parentheses. This is where I got, adapting Skillmon's answer.

\DeclarePairedDelimiterXPP\funcarg[2]{#1}\lparen\rparen{}{#2}


\ExplSyntaxOn
\cs_set:Npn \__parseiv:n #1 {%
    \bool_lazy_any:nTF {{\tl_if_empty_p:n {#1}} {\tl_if_blank_p:n {#1}}} {\cdot} {#1}
}
\keys_define:nn {nmt/func} {%
    size .tl_set:N   = \l__nmt_size,
    ph .bool_set:N = \l__nmt_ph
}
\NewDocumentCommand\funcgen{sO{}mO{}}{%
    \IfBooleanTF{#1}{%
        \funcarg*{#3}{\__parseiv:n {#4}}
    }{%
        \group_begin:
            \keys_set:nn {nmt/func} {#2}
            
            \tl_if_empty:NTF \l__nmt_size {%
                \bool_if:NTF \l__nmt_ph {%
                    \funcarg{#3}{\__parseiv:n{#4}}%
                    }{%
                    \tl_if_empty:nTF {#4} {#3} {\funcarg{#3}{\__parseiv:n {#4}}}%
                }%
            }{%
                \exp_last_unbraced:NNo \funcarg[\l__nmt_size]{#3}{\__parseiv:n {#4}}%
            }
        \group_end:
      }%
}
\ExplSyntaxOff

\NewDocumentCommand\Gm{sO{}m}{\IfBooleanTF{#1}{\funcgen*}{\funcgen}[#2]{\Gamma}[#3]}

Example of result:

\[\funcgen{f}[] \quad ; \quad \funcgen[ph=true]{f} \quad ; \quad \funcgen[ph=true,size=\Big]{f} \quad ; \quad \funcgen[ph=false,size=\Big]{f}\]

\[\funcgen{f}[x] \quad ; \quad \funcgen[ph=false]{f}[x]\]

\[\funcgen*{f} \quad ; \quad \funcgen*{f}[\frac{x}{2}] \quad ; \quad \funcgen*[size=\big]{f}[\frac{x}{2}]\]

\[f(\frac{x}{2}) \quad ; \quad f\left(\frac{x}{2}\right) \quad ; \quad \funcgen[size=\Big]{f}[\frac{x}{2}] \quad ; \quad \funcgen[size=\big]{f}[\frac{x}{2}]\]

\[\Gamma(\frac{\beta}{2}) \quad ; \quad \Gamma\left(\frac{\beta}{2}\right) \quad ; \quad \funcgen*{\Gamma}[\frac{\beta}{2}] \quad ; \quad \funcgen[size=\Big]{\Gamma}[\frac{\beta}{2}]\]

\[\Gm{} \quad ; \quad \Gm{\frac{\beta}{2}} \quad ; \quad \Gm*{\frac{\beta}{2}} \quad ; \quad \funcgen[ph=true]{\Gamma}[]\]

keyval implementation results

That is when I realised I could scrap the keys altogether, and rely exclusively on whether the star version is called and testing the value of the last argument to achieve what I wanted.

\ExplSyntaxOn
\cs_set:Npn \__parseiv_ii:n #1 {%
    \tl_if_blank:nTF {#1} {\cdot} {#1}
}
\NewDocumentCommand\funcgenii{somO{}}{%
    \IfBooleanTF{#1}{%
        \funcarg*{#3}{\__parseiv_ii:n {#4}}
    }{%
        \tl_if_novalue:nTF {#2} {%
            \tl_if_empty:nTF {#4} {#3} {\funcarg{#3}{\__parseiv_ii:n {#4}}}%
        }{%
            \funcarg[#2]{#3}{\__parseiv_ii:n {#4}}%
        }
    }%
}
\ExplSyntaxOff

\NewDocumentCommand\Gmii{som}{\IfBooleanTF{#1}{\funcgenii*}{\funcgenii}[#2]{\Gamma}[#3]}

Much shorter, probably easier to read, and more straightforward to use. The sameish example as above becomes

\[\funcgenii{f}[] \quad ; \quad \funcgenii*{f} \quad ; \quad \funcgenii[\Big]{f} \quad ; \quad \funcgenii*[\Big]{f}\]

\[\funcgenii{f}[x]\]

\[\funcgenii*{f}[\frac{x}{2}] \quad ; \quad \funcgenii*[\big]{f}[\frac{x}{2}] \quad ; \quad \funcgenii[\big]{f}[\frac{x}{2}]\]

\[f(\frac{x}{2}) \quad ; \quad f\left(\frac{x}{2}\right) \quad ; \quad \funcgenii[\Big]{f}[\frac{x}{2}]\]

\[\Gamma(\frac{\beta}{2}) \quad ; \quad \Gamma\left(\frac{\beta}{2}\right) \quad ; \quad \funcgenii*{\Gamma}[\frac{\beta}{2}] \quad ; \quad \funcgenii[\Big]{\Gamma}[\frac{\beta}{2}]\]

\[\Gmii{} \quad ; \quad \Gmii{\frac{\beta}{2}} \quad ; \quad \Gmii[\big]{\frac{\beta}{2}} \quad ; \quad \Gmii*{\frac{\beta}{2}} \quad ; \quad \funcgenii*{\Gamma}[]\]

star only implementation results

As my definitions of \Gm and \Gmii illustrate, the goal is to use \genfunc as a template for other functions, that have to define the mandatory argument. I am just not very satisfied with the way I test for the star before passing to \genfunc (\IfBooleanTF{#1}{\funcgenii*}{\funcgenii}[#2]{\Gamma}[#3]) and I wonder if that can be improved upon.

Other comments more than welcome as well!

6
  • 1
    Perhaps it would all be less confusing if you provided the context where you would like to use this interface.
    – Gaussler
    Oct 31, 2022 at 7:07
  • your 4 and 5 seem wrong (although your question is far from clear, and you provided no test document) but assuming \macroa has sO{}mO{} signature, 4 and 5 should be \macroa{expr}[opt] not \macroa{expr}{opt} Oct 31, 2022 at 9:19
  • @DavidCarlisle I assume 1 is wrong, and the signature of \macroa is somm or sO{}mm.
    – Skillmon
    Oct 31, 2022 at 9:22
  • @Skillmon ah possibly but why doesn't the question describe this? :-) Oct 31, 2022 at 9:23
  • In 2 you find "ignoring the second key" while there you find only "key1=val1". What are key2 and "second key" about? Oct 31, 2022 at 10:10

3 Answers 3

4

If I understand well, you want to control the size of parentheses which are used around functional parameter. Your suggested syntax is not suitable for humans but for machines. We often forget that the TeX source is designed for human, i.e. humans create them in text editors, humans read them during checking the source. The syntax must be designed for humans. It means, that if I want to print f(x) then I write f(x) in the source without any optional parameters, key/valued settings, starred versions of macros etc.

Of course, if a user want to print f(x(y+z)) then obviously he want to have the outer parentheses bigger. The f\bigl(x(y+z)\bigr) is correct but it looks ugly from human eyes point of view. This is IMHO the start of your ideas. But your syntax is not human comfortable. It is much better looking \bigp f(x(y+z)) which expands to the previous example.

I suggest macros \bigp, \Bigp, \biggp, \Biggp which can be used as a prefix before the functional symbol. The following parameter can be surrounded to (...), or [...] or \{...\} or {...} and the given parentheses are printed bigger. For example \Bigp F[x+y] prints the same as F\Bigl[x+y\bigr]. The parameter text of the functional is interpreted as balanced text with respect to the given parentheses. Moreover, the parameter has its own group, so one can write \Bigp R(a\over b+c) and it prints R\Bigl({a\over b+c}\Bigr). We design the syntax for humans, not for machines.

The implementation looks like:

\def\bigp#1{#1\fparam\bigl\bigr}
\def\Bigp#1{#1\fparam\Bigl\Bigr}
\def\biggp#1{#1\fparam\biggl\biggr}
\def\Biggp#1{#1\fparam\Biggl\Biggr}
\def\autop#1{#1\fparam\left\right}    % for auto-sized parentheses
\def\normalp#1{#1\fparam\relax\relax} % for no-scaled parenthesed
\def\fparam#1#2{\let\bigleft=#1\let\bigright=#2\futurelet\next\fparamA}
\def\fparamA{%
   \ifx\next(\fparamB()\fi
   \ifx\next[\fparamB[]\fi
   \ifx\next\{\fparamB\{\}\fi
   \ifx\next\bgroup \def\lparen{\{}\def\rparen{\}}\afterrelax{\fparamC}\fi
   \relax
}
\def\fparamB#1#2#3\relax{\fi
   \def\lparen{#1}\def\rparen{#2}%
   \def\next#1##1#2{\ensurebalanced#1#2\fparamC{##1}}%
   \next
}
\def\fparamC#1{%
   \ifx\bigleft\left \mathopen{}\fi
   \bigleft\lparen{#1}\bigright\rparen
   \ifx\bigright\right \mathclose{}\fi
}
\def\afterrelax#1#2\relax{\fi#1}

We need the macro \ensurebalanced which implements the scanner of balanced text by different parentheses than {...}. I borrowed this from OpTeX trick 0043 with minor changes in order to it works in pdfTeX too.

\newcount\tmpnum
\def\ensurebalanced#1#2#3{%
   \def\balopen{#1}\def\balclose{#2}\let\balaction=#3%
   \def\readnextbal##1##2#2{\ensurebalancedA{##1#2##2}}%
   \ensurebalancedA}
\def\ensurebalancedA#1{\isbalanced#1%
   \iftrue\afterfi{\balaction{#1}}\else\afterfi{\readnextbal{#1}}\fi}
\def\isbalanced#1\iftrue{\tmpnum=0 \isbalancedA#1{\isbalanced}}
\def\isbalancedA#1#{\countbalanced#1\isbalanced \isbalancedB}
\def\isbalancedB#1{%
   \ifx\isbalanced#1\afterfi{\csname ifnum\endcsname\tmpnum=0 }\else\expandafter\isbalancedA\fi}
\def\countbalanced#1{\expandafter\ifx\balopen #1\advance\tmpnum by1 \fi
                     \expandafter\ifx\balclose#1\advance\tmpnum by-1 \fi
                     \ifx\isbalanced#1\else\expandafter\countbalanced\fi}
\def\afterfi#1#2\fi{\fi#1}

Now, we can do tests similar to your:

$$
  f(x(y+z)), \quad  \bigp f(x(y+z)),\quad \autop f(a\over b), \quad \Bigp f(a\over b+c),
$$
$$
  f[x(y+z)], \quad  \bigp f[x(y+z)],\quad \autop f[a\over b], \quad \Bigp f{a\over b+c},
$$
$$
  f ; \quad f(\cdot) ; \quad \Bigp f(\cdot) ; 
$$
$$
  f(x)
$$
$$
  f({x\over2}) ; \quad \normalp f(x\over2) ; \quad \autop f(x^2\over2) ; \quad \Bigp f(x^2\over2)
$$
$$
  \Gamma ({\beta\over2}) ; \quad \autop\Gamma (\beta\over2) ; \quad \Bigp\Gamma (\beta\over2) 
$$
$$
  \Gamma ; \quad \normalp\Gamma (\beta\over2) ; \quad \autop\Gamma (\beta\over2) ; \quad \Gamma (\cdot)
$$

The result looks like this:

fparam

1
  • Plain TeX is “not suitable for humans but for machines”. ;-)
    – Gaussler
    Nov 1, 2022 at 11:45
3

This answer presents a few solutions, two of which use my own expkv-family of packages, the third uses the l3keys module of expl3.


expkv-cs

The following uses expkv-cs for your key=value interface, as it's quite simple to set up, and you get the key values as macro arguments.

I changed your \macrob signature to sO{}mo, otherwise the (in Python mock-up) if key2 or opt_arg couldn't be checked, and the key=value approach needs less checks if the interface just gets an empty value instead of needing to check there with yet another \IfNoValueTF. Else this is pretty much just a bunch of tests whether some value was there or not, due to the assignment-free nature of expkv-cs the key2 is implemented as an enum-type, meaning true gets forwarded as a 1 and false as a 0, this is then evaluated using \ifodd.

\documentclass{article}

\usepackage{expkv-cs}

\makeatletter
\NewDocumentCommand \macroa { somm }
  {%
    \begin{tabular}{@{}ll@{}}
      \hline
            & \IfBooleanF{#1}{No~}Star \\
      Opt:  & #2 \\
      Arg1: & #3 \\
      Arg2: & #4 \\
      \hline
    \end{tabular}%
  }

\NewDocumentCommand \macrob { sO{}mo }
  {%
    \IfBooleanTF{#1}%
      {%
        \IfNoValueTF{#4}%
          {\macroa*{#3}{}}%
          {\macroa*{#3}{#4}}%
      }%
      {\macrob@KV{#2}{#3}{#4}}%
  }
\expanded{\unexpanded{\ekvcSplitAndForward\macrob@KV\macrob@DO}%
  {%
     key1  = \csname c_novalue_tl\endcsname
    ,key2-internal = 0
  }
}
\ekvcSecondaryKeys\macrob@KV
  {
    enum key2 = {key2-internal}{false,true}
  }
\newcommand\macrob@DO[4]
  {%
    \IfValueTF{#1}%
      {%
        \IfNoValueTF{#4}%
          {\macroa[#1]{#3}{}}%
          {\macroa[#1]{#3}{#4}}%
      }%
      {%
        \IfNoValueTF{#4}%
          {%
            \ifodd#2
              \expandafter\@firstoftwo
            \else
              \expandafter\@secondoftwo
            \fi
            {\macroa{#3}{}}%
            {#3}%
          }%
          {\macroa{#3}{#4}}%
      }%
  }

\begin{document}
\macrob*{expr}

\macrob[key1=val1]{expr}[opt]

\macrob[key2=true]{expr}[opt]

\macrob[key2=true]{expr}

\macrob{expr}[opt]

\macrob{expr}
\end{document}

expkv-def

Another solution could use expkv-def, which is similar to l3keys or other more traditional key=value solutions, supporting different key types.

\documentclass{article}

\usepackage{expkv-def}

\makeatletter
\NewDocumentCommand \macroa { somm }
  {%
    \begin{tabular}{@{}ll@{}}
      \hline
            & \IfBooleanF{#1}{No~}Star \\
      Opt:  & #2 \\
      Arg1: & #3 \\
      Arg2: & #4 \\
      \hline
    \end{tabular}%
  }

\NewDocumentCommand \macrob { sO{}mo }
  {%
    \IfBooleanTF{#1}%
      {%
        \IfNoValueTF{#4}%
          {\macroa*{#3}{}}%
          {\macroa*{#3}{#4}}%
      }%
      {%
        \begingroup % keeping the scope of the set keys local
          \ekvset{macrob}{#2}%
          \macrob@opt
            {\macrob@opt@aux{#3}{#4}}%
            {%
              \IfNoValueTF{#4}%
                {%
                  \macrob@bool
                    {\macroa{#3}{}}%
                    {#3}%
                }%
                {\macroa{#3}{#4}}%
            }%
        \endgroup
      }%
  }
\newcommand\macrob@opt@aux[3]
  {%
    \IfNoValueTF{#2}%
      {\macroa[#3]{#1}{}}%
      {\macroa[#3]{#1}{#2}}%
  }
\ekvdefinekeys{macrob}
  {
     data key1   = \macrob@opt
    ,boolTF key2 = \macrob@bool
  }

\begin{document}
\macrob*{expr}

\macrob[key1=val1]{expr}[opt]

\macrob[key2=true]{expr}[opt]

\macrob[key2=true]{expr}

\macrob{expr}[opt]

\macrob{expr}
\end{document}

l3keys

This is similar to expkv-def, but l3keys has nothing comparable to the data key type. Instead this tests whether the macro storing the value is empty. This way, key1={} is indistinguishable from key1 not being used at all. Of course you could do something similar to the expkv-cs variant, and use \c_novalue_tl as the initial value of key1 and can then use \tl_if_eq:NNTF \c_novalue_tl \l__macrob_key_tl to test whether the key was used.

\documentclass{article}

\makeatletter
\NewDocumentCommand \macroa { somm }
  {%
    \begin{tabular}{@{}ll@{}}
      \hline
            & \IfBooleanF{#1}{No~}Star \\
      Opt:  & #2 \\
      Arg1: & #3 \\
      Arg2: & #4 \\
      \hline
    \end{tabular}%
  }

\ExplSyntaxOn
\NewDocumentCommand \macrob { sO{}mo }
  {%
    \IfBooleanTF{#1}%
      {%
        \IfNoValueTF{#4}%
          {\macroa*{#3}{}}%
          {\macroa*{#3}{#4}}%
      }%
      {%
        \group_begin:
          \keys_set:nn { macrob } {#2}
          \tl_if_empty:NTF \l__macrob_key_tl
            {
              \IfNoValueTF {#4}
                {
                  \bool_if:NTF \l__macrob_key_bool
                    { \macroa {#3} {} }
                    { #3 }
                }
                { \macroa {#3} {#4} }
            }
            {
              \IfNoValueTF {#4}
                { \exp_last_unbraced:NNo \macroa [ \l__macrob_key_tl ] {#3} {} }
                { \exp_last_unbraced:NNo \macroa [ \l__macrob_key_tl ] {#3} {#4} }
            }
        \group_end:
      }%
  }
\keys_define:nn { macrob }
  {
     key1 .tl_set:N   = \l__macrob_key_tl
    ,key2 .bool_set:N = \l__macrob_key_bool
  }
\ExplSyntaxOff

\begin{document}
\macrob*{expr}

\macrob[key1=val1]{expr}[opt]

\macrob[key2=true]{expr}[opt]

\macrob[key2=true]{expr}

\macrob{expr}[opt]

\macrob{expr}
\end{document}

Result

All three look the same:

enter image description here

1

The package semantex (disclaimer: I am the author) was made for providing more or less exactly this kind of syntax:

\documentclass{article}

\usepackage{semantex} % requires version 0.523,
                      % released on 2022/12/03

\NewVariableClass\MyVar[output=\MyVar, set arg slot={{\cdot}}]

\begin{document}

\( \MyVar{f}, \MyVar{f}{---} , \MyVar{f}[par=\Big]{---} , \MyVar{f}{x} \)

\NewObject\MyVar\vf{f}
\NewObject\MyVar\vx{x}

\( \vf , \vf{---}, \vf[par=\Big]{---}, \vf{x}, \vf{\vx} \)

\NewObject\MyVar\Gm{\Gamma}

\( \Gm, \Gm{ \frac{\beta}{2} }, \Gm[par=\Big]{ \frac{\beta}{2} } , \Gm{---} \)

\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 .