3

I'm sorry if the title suggests a question that has been asked a lot of times (see below), but I couldn't find an answer that also fulfills another need...

As a math teacher, I want my students to get used (at least at the beginning) to always write parenthesis around functions like cos, sin, tan, log, ln, etc., so to have cos(x) instead of cos x. I therefore wanted my LaTeX files to always put (...) around those functions automatically, by calling them as \cos{x}.

For this, I already found more than one question/answer that allows me to achieve this, e.g.

I chose the following version from the first link:

\makeatletter
\newcommand*{\redefinesymbolwitharg}[1]{%
  \expandafter\let\csname ltx#1\expandafter\endcsname\csname #1\endcsname
  \@namedef{#1}{\@ifnextchar{^}{\@nameuse{#1@}}{\@nameuse{#1@}^{}}}%
  \expandafter\def\csname #1@\endcsname^##1##2{%
     \csname ltx#1\endcsname\ifx!##1!\else^{##1}\fi\mathopen{}\mathclose\bgroup\left(##2\aftergroup\egroup\right)
     }%
}
\makeatother

and then call for example

\redefinesymbolwitharg{cos}

The above code allows me to write

\cos x + \cos{y} + \cos^{2}{z} + \cos^3{k}

by always adding the bracket around the argument, but not to write

\cos_a{x}

Trying to understand the above code, I see that the @ifnextchar{^} case is specifically handled, but I've not been able to also integrate the "_" case, necessary for e.g. the \log function. Could someone help me to add this functionality to the above code (or suggest another way of achieving this?).

Thank you in advance for any suggestion! :)


Bonus question:

Would it also be possible to integrate the case \cos'{x} to be converted in \cos^{'}{x}, and so on for ' ' and ' ' ' ? This is not mandatory, but would improve the writing of a lot of lines...

16
  • 1
    Welcome to TeX.SE. – Mico Apr 14 '20 at 17:50
  • 1
    Does a solution have to be pdflatex-based, or are you willing to contemplate lualatex-based solutions? Please advise. – Mico Apr 14 '20 at 17:51
  • 1
    if you are anyway adding braces around the argument as in \cos{y} why not simply use parens instead \cos(y) it requires no definition and is much less likely to conflict with other math macro packages, and requires just the same number of characters in the input? – David Carlisle Apr 14 '20 at 19:45
  • 2
    Oh well, I didn't see your reply comment until I already gone ahead and posted a LuaLaTeX-based answer. Hopefully it'll still be useful for some other readers in the future. – Mico Apr 14 '20 at 21:09
  • 2
    Off-topic: You really don't want to use f^{'}. See difference between \prime and ' in math mode – barbara beeton Apr 14 '20 at 23:32
3

You say:

As a math teacher, I want my students to get used (at least at the beginning) to always write parenthesis around functions like cos, sin, tan, log, ln, etc.

... while it seems that you yourself wish to get used to not having to do so at all in your .tex-input-files. ;-)

By the way:

What exactly do you want the students to write in parentheses?
The sequence of characters that designates the (name of the) function in question?
Sequences of characters that designate operands/arguments of functions?
Please be precise when imposing new things on learners.

\cos, \sin, \tan, \log, \ln are macros which don't process arguments as they serve for typesetting the sequence of characters that designates the (name of the) function in question.

With$\cos$ (La)TeX does nicely typeset the phrase "cos".

With$\cos{x}$ (La)TeX does nicely typeset the phrase "cos". Then the sequence {x} is processed, yielding the letter x in math-font. The curly braces in math-mode prevent linebreaks with the things nested within them. Thus in this situation they are not needed as there is only a single character nested between them which makes considerations regarding linebreaks obsolete. (In case I am wrong here/overlooking something here, please drop me a hint and I will correct my answer immediately.)

You actually ask for a mechanism which automatically detects what belongs to the arguments/operands of the function in question. (←The further.)
You may also need a mechanism which automatically detects whether subscripts/​superscripts are to be put between parentheses. (I think this should be done in cases where the subscript/superscript contains other things than just a single number or just a single variable.) (←The latter.)

In the example below I did not try to implement the latter.

I only tried to implement the further.

I did so by means of an \@ifnextchar-based recursive loop which gathers up things until finding a token whose meaning neither equals _ nor equals ^ nor equals '. As long as you don't do things like \cos\empty\relax{(x)}, this probably might be sufficient.

\documentclass[landscape, oneside]{article}
%===================[adjust margins/layout for the example]====================
\csname @ifundefined\endcsname{pagewidth}{}{\pagewidth=\paperwidth}%
\csname @ifundefined\endcsname{pdfpagewidth}{}{\pdfpagewidth=\paperwidth}%
\csname @ifundefined\endcsname{pageheight}{}{\pageheight=\paperheight}%
\csname @ifundefined\endcsname{pdfpageheight}{}{\pdfpageheight=\paperheight}%
\textwidth=\paperwidth
\oddsidemargin=1.25cm
\marginparsep=.2\oddsidemargin
\marginparwidth=\oddsidemargin
\advance\marginparwidth-2\marginparsep
\advance\textwidth-2\oddsidemargin
\advance\oddsidemargin-1in
\evensidemargin=\oddsidemargin
\textheight=\paperheight
\topmargin=1.25cm
\footskip=.5\topmargin
{\normalfont\global\advance\footskip.5\ht\strutbox}%
\advance\textheight-2\topmargin
\advance\topmargin-1in
\headheight=0ex
\headsep=0ex
\pagestyle{plain}
\parindent=0ex
\parskip=0ex 
\topsep=0ex
\partopsep=0ex
%==================[eof margin-adjustments]====================================

\makeatletter

\newcommand\gatherargumentloop[1]{%
  % #1 - amount of arguments/operands to gather.
  % Let's create an amount of catcode-12-characters "m" equal to the amount of
  % arguments to gather.
  \expandafter\@gatherargumentloop\expandafter{\romannumeral\number\number#1 000}%
}%
\newcommand\@gatherargumentloop[1]{%
  \if\relax#1\relax\expandafter\@gobble\else\expandafter\@firstofone\fi
  {%
    \@ifnextchar^{\gathersupscript{#1}}{%
      \@ifnextchar_{\gathersubscript{#1}}{%
        \@ifnextchar'{\gatherprimesloop{#1}{}}{%
          \gatherargument{#1}%
        }%
      }%
    }%
  }%
}%
\@ifdefinable\gatherprimesloop{%
  \long\def\gatherprimesloop#1#2'{%
    \@ifnextchar'{\gatherprimesloop{#1}{#2'}}{{#2'}\@gatherargumentloop{#1}}%
  }%
}%
\@ifdefinable\gathersupscript{%
  \long\def\gathersupscript#1^#2{%
     ^{#2}\@gatherargumentloop{#1}%
  }%
}%
\@ifdefinable\gathersubscript{%
  \long\def\gathersubscript#1_#2{%
     _{#2}\@gatherargumentloop{#1}%
  }%
}%
\newcommand\gatherargument[1]{%
  \@ifnextchar({\@@gatherargument{#1}}{\@gatherargument{#1}}%
}%
\newcommand\@gatherargument[2]{%
  \UD@CheckWhetherLeadingTokens{#2}{\left(}{.}{\LeftLeftParen}{#2}{%
    \UD@CheckWhetherLeadingTokens{#2}{(}{.}{\LeftParen}{#2}{%
      \UD@CheckWhetherLeadingTokens{#2}{ (}{.}{\SpaceLeftParen}{#2}{%
        \UD@CheckWhetherLeadingTokens{#2}{ \left(}{.}{\SpaceLeftLeftParen}{#2}{%
          (#2)%
        }%
      }%
    }%
  }% 
  \expandafter\@gatherargumentloop\expandafter{\@gobble#1}%
}%
\@ifdefinable\@@gatherargument{%
  \long\def\@@gatherargument#1#2){%
    #2)%
    \expandafter\@gatherargumentloop\expandafter{\@gobble#1}%
  }%
}%
%==========[code for checking leading token-sequences in arguments]============
%% Check whether argument is empty:
%%.............................................................................
%% \UD@CheckWhetherNull{<Argument which is to be checked>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is empty>}%
%%                     {<Tokens to be delivered in case that argument
%%                       which is to be checked is not empty>}%
%%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
\newcommand\UD@CheckWhetherNull[1]{%
  \romannumeral0\expandafter\@secondoftwo\string{\expandafter
  \@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
  \@secondoftwo\string}\expandafter\@firstoftwo\expandafter{\expandafter
  \@secondoftwo\string}\@firstoftwo\expandafter{} \@secondoftwo}%
  {\@firstoftwo\expandafter{} \@firstoftwo}%
}%
%
%%-----------------------------------------------------------------------------
%% Exchange two arguments. (From each argument an outermost level of 
%% surrounding braces will be removed if present.)
%%-----------------------------------------------------------------------------
\newcommand\UD@Exchange[2]{#2#1}%
%%-----------------------------------------------------------------------------
%% Check whether argument's leading tokens form a specific 
%% token-sequence that does not contain explicit character tokens of 
%% category code 1 or 2:
%%.............................................................................
%% \UD@CheckWhetherLeadingTokens{<argument which is to be checked>}%
%%                              {<a <token sequence> without explicit 
%%                                character tokens of category code
%%                                1 or 2>}%
%%                              {a <single non-space token> that does 
%%                                _not_ occur in <token sequence> >}%
%%                              {<internal token-check-macro>}%
%%                              {<tokens to be delivered in case
%%                                <argument which is to be checked> has
%%                                <token sequence> as leading tokens>}%
%%                              {<tokens to be delivered in case 
%%                                <argument which is to be checked>
%%                                does not have <token sequence> as
%%                                leading tokens>}%
\newcommand\UD@CheckWhetherLeadingTokens[4]{%
  \romannumeral0\UD@CheckWhetherNull{#1}%
  {\UD@Exchange{ }\expandafter\@secondoftwo}%
  {\expandafter\@secondoftwo\string{\expandafter
   \UD@@CheckWhetherLeadingTokens#4#3#1#2}{}}%
}%
\newcommand\UD@@CheckWhetherLeadingTokens[1]{%
  \expandafter\UD@CheckWhetherNull\expandafter{\@firstoftwo{}#1}%
  {\UD@Exchange{\@firstoftwo}}{\UD@Exchange{\@secondoftwo}}%
  {\UD@Exchange{ }{\expandafter\expandafter\expandafter\expandafter
   \expandafter\expandafter\expandafter}\expandafter\expandafter
   \expandafter}\expandafter\@secondoftwo\expandafter{\string}%
}%
%%-----------------------------------------------------------------------------
%% \UD@internaltokencheckdefiner{<internal token-check-macro>}%
%%                              {<token sequence>}%
%% Defines <internal token-check-macro> to snap everything 
%% until reaching <token sequence>-sequence and spit that out
%% nested in braces.
%%-----------------------------------------------------------------------------
\newcommand\UD@internaltokencheckdefiner[2]{%
  \@ifdefinable#1{\long\def#1##1#2{{##1}}}%
}%
\UD@internaltokencheckdefiner{\LeftLeftParen}{\left(}%
\UD@internaltokencheckdefiner{\LeftParen}{(}%
\UD@internaltokencheckdefiner{\SpaceLeftParen}{ (}%
\UD@internaltokencheckdefiner{\SpaceLeftLeftParen}{ \left(}%
%=======[end ofcode for checking leading token-sequences in arguments]=========

\makeatother

\newcommand\mycos{\cos\gatherargumentloop{1}}
\newcommand\mysin{\sin\gatherargumentloop{1}}
\newcommand\mytan{\tan\gatherargumentloop{1}}
\newcommand\mycot{\cot\gatherargumentloop{1}}
\newcommand\mylog{\log\gatherargumentloop{1}}
\newcommand\myln{\ln\gatherargumentloop{1}}

% Like \cos, \sin, \tan, \log, \ln, but with two operands:
\makeatletter
\newcommand\add{\mathop{\operator@font add}\nolimits}%
\makeatother

\newcommand\myadd{\add\gatherargumentloop{2}}

\begin{document}

The $\cos$-game:

\medskip

{\footnotesize\verb|$\mycos x + \mycos{y} + \mycos^{2+3}{z} + \mycos^3{k} + \mycos_{a_1}{x} + \mycos'''''_{a_1}{x} +  \mycos'''''_{a_1}(x) + \mycos'''''_{a_1}{(x)}$|:}

$\mycos x + \mycos{y} + \mycos^{2+3}{z} + \mycos^3{k} + \mycos_{a_1}{x} + \mycos'''''_{a_1}{x} +  \mycos'''''_{a_1}(x) + \mycos'''''_{a_1}{(x)}$

\bigskip

The $\sin$-game:

\medskip

{\footnotesize\verb|$\mysin x + \mysin{y} + \mysin^{2+3}{z} + \mysin^3{k} + \mysin_{a_1}{x} + \mysin'''''_{a_1}{x} + \mysin'''''_{a_1}(x) + \mysin'''''_{a_1}{(x)}$|:}

$\mysin x + \mysin{y} + \mysin^{2+3}{z} + \mysin^3{k} + \mysin_{a_1}{x} + \mysin'''''_{a_1}{x} + \mysin'''''_{a_1}(x) + \mysin'''''_{a_1}{(x)}$

\bigskip

The $\tan$-game:

\medskip

{\footnotesize\verb|$\mytan x + \mytan{y} + \mytan^{2+3}{z} + \mytan^3{k} + \mytan_{a_1}{x} + \mytan'''''_{a_1}{x} + \mytan'''''_{a_1}(x) + \mytan'''''_{a_1}{(x)}$|:}

$\mytan x + \mytan{y} + \mytan^{2+3}{z} + \mytan^3{k} + \mytan_{a_1}{x} + \mytan'''''_{a_1}{x} + \mytan'''''_{a_1}(x) + \mytan'''''_{a_1}{(x)}$

\bigskip

The $\cot$-game:

\medskip

{\footnotesize\verb|$\mytan x + \mytan{y} + \mytan^{2+3}{z} + \mytan^3{k} + \mytan_{a_1}{x} + \mytan'''''_{a_1}{x} + \mytan'''''_{a_1}(x) + \mytan'''''_{a_1}{(x)}$|:}

$\mycot x + \mycot{y} + \mycot^{2+3}{z} + \mycot^3{k} + \mycot_{a_1}{x} + \mycot'''''_{a_1}{x} + \mycot'''''_{a_1}(x) + \mycot'''''_{a_1}{(x)}$

\bigskip

The $\log$-game:

\medskip

{\footnotesize\verb|$\mylog x + \mylog{y} + \mylog^{2+3}{z} + \mylog^3{k} + \mylog_{a_1}{x} + \mylog'''''_{a_1}{x} + \mylog'''''_{a_1}(x) + \mylog'''''_{a_1}{(x)}$|:}

$\mylog x + \mylog{y} + \mylog^{2+3}{z} + \mylog^3{k} + \mylog_{a_1}{x} + \mylog'''''_{a_1}{x} + \mylog'''''_{a_1}(x) + \mylog'''''_{a_1}{(x)}$

\bigskip

The $\ln$-game:

\medskip

{\footnotesize\verb|$\myln x + \myln{y} + \myln^{2+3}{z} + \myln^3{k} + \myln_{a_1}{x} + \myln'''''_{a_1}{x} + \myln'''''_{a_1}(x) + \myln'''''_{a_1}(x)$|:}

$\myln x + \myln{y} + \myln^{2+3}{z} + \myln^3{k} + \myln_{a_1}{x} + \myln'''''_{a_1}{x} + \myln'''''_{a_1}(x) + \myln'''''_{a_1}(x)$

\bigskip

The $\add$-game:

\medskip

{\footnotesize\verb|$\myadd xy + \myadd{x}{y} + \myadd^{2+3}{y}{z} + \myadd^3{k}{l} + \myadd_{a_1}{x}{y} + \myadd'''''_{a_1}{x}{y} + \myadd'''''_{a_1}(x)(y) + \myadd'''''_{a_1}{(x)}{(y)}$|:}

$\myadd xy + \myadd{x}{y} + \myadd^{2+3}{y}{z} + \myadd^3{k}{l} + \myadd_{a_1}{x}{y} + \myadd'''''_{a_1}{x}{y} + \myadd'''''_{a_1}(x)(y) + \myadd'''''_{a_1}{(x)}{(y)}$

\end{document}

enter image description here

15
  • 2
    When a user-defined command is used in a "moveable" argument, it needs to be protected. (\section is moveable, as it produces an entry for a table of contents.) In such a case, enter \protect\mysin, etc. – barbara beeton Apr 15 '20 at 18:28
  • 1
    @MattiaBergomi I am glad that you managed to do everything the way you need it. I tend not to redefine default-macros but to write macros with different names. This ensures that macros/macro-packages where it is relied on the defaults being not redefined are not broken by my code. – Ulrich Diez Apr 15 '20 at 20:21
  • 1
    @MattiaBergomi About \let\Oldcos=\cos: You can do the same with my code: In the definition of \mycos replace \cos by \Oldcos. Then replace \newcommand\mycos by \let\Oldcos=\cos \renewcommand\cos. As said in my previous comment, I don't recommend patching default-commands, but one of the good things about LaTeX is that everyone is free to design their code according to their own ideas/likings. Patching a LaTeX-command is not always that trivial. Macros defined by \DeclareRobustCommand or by \newcommand with optional arguments underlies a mechanisms consisting of several macros. – Ulrich Diez Apr 15 '20 at 20:34
  • 1
    @MattiaBergomi I think teaching kids to use parentheses is a good thing. Not just because of functions. Also because of unambiguity. E.g., cos5/cos3 - what is it? Is it cos(5/(cos(3))) or is it (cos(5))/(cos(3)) ? ;-) When it comes to math, I'm a parentheses-fetishist. ;-) Since I usually don't get along well with calculators -I often make mistakes when typing - , I do a lot of calculations on paper or using programming languages. Placing parentheses helps me to find my errors... And by the way: I saw: sin(x)/cos(x) = in/co ... ;-) – Ulrich Diez Apr 15 '20 at 20:47
  • 1
    @MattiaBergomi I put code into backticks: Here is some code: `\def\firstoftwo#1#2{#1}` Here is no more code. If the code itself contains backticks, too, wrap it into more backticks than occur in the code: Here is some code with backticks in it:```\catcode`\a=11\relax```. No more code. If you click on "help" while writing a comment, you will be shown a short summary of mini-Markdown-formatting within comments. – Ulrich Diez Apr 16 '20 at 20:17
3

Here's a LuaLaTeX-based solution which works for expressions involving \sin, \cos, \tan, and \cot. Their argument can be either 1 or more letters (e.g, x or xy) or \ followed immediately by one or more letters, e.g., \theta or \Omega. The argument can be either free-standing or enclosed in curly braces. There can also be an superscript term, e.g, ^2, n, or (not sure a sane person would ever write this!) (3!).

The code consists of a Lua function called add_parens that does all of the work and two LaTeX macros, called \AddParensOn and \AddParensOff, which activate and deactivate the Lua function. The Lua function performs a lot of separate string substitution operations because, e.g, both \sin x and \sin {x} have to be changed to \sin(x). And, of course, the code mustn't get tripped up if it encounters \sin(x), i.e., no extra pairs of parentheses must be added in such cases. If you can limit your input text so as to avoid \sin{x}, then the substitution operations can be simplified considerably.

Handling expressions with subscripts, e.g, \log_2 8 or \log_{10} 1000, is left as an exercise for the reader. :-)

enter image description here

% !TEX TS-program = lualatex
\documentclass{scrreprt}
\usepackage{luacode}
%% The 'add_parens' Lua function does most of the work:
\begin{luacode}
function add_parens ( s )
  ll= { "sin" , "cos" , "tan" , "cot" }
  for i=1,#ll do
    s = s:gsub ( "(\\"..ll[i]..")%s+(%a+)"   , "%1(%2)" ) -- \sin x
    s = s:gsub ( "(\\"..ll[i]..")%s-(\\%a+)" , "%1(%2)" ) -- \cos\alpha
    s = s:gsub ( "(\\"..ll[i]..")%s-(%b{})"  , "%1(%2)" ) -- \tan{y}
    s = s:gsub ( "(\\"..ll[i]..")%s-(^%s-%w)%s-(\\?%a+)"  , "%1%2(%3)" ) -- \sin^2 z 
    s = s:gsub ( "(\\"..ll[i]..")%s-(^%s-%w)%s-(%b{})"    , "%1%2(%3)" ) -- \sin^3 {u}
    s = s:gsub ( "(\\"..ll[i]..")%s-(^%s-%b{})%s-(\\?%a+)", "%1%2(%3)" ) -- \cos ^{2} \pi
    s = s:gsub ( "(\\"..ll[i]..")%s-(^%s-%b{})%s-(%b{})"  , "%1%2(%3)" ) -- \cos ^{10} {v}
  end  
  return s
end
\end{luacode}

%% Define two "toggle switch" LaTeX macros:
\newcommand\AddParensOn{\directlua{ luatexbase.add_to_callback 
  ( "process_input_buffer" , add_parens , "AddParens" )}}
\newcommand\AddParensOff{\directlua{ luatexbase.remove_from_callback 
  ( "process_input_buffer" , "AddParens" )}}

\begin{document}
$\sin\alpha \cos {\gamma} \tan xy \sin{z} \cos^2x \cot ^ {(3!)} {\omega}$ 

\medskip
\AddParensOn % enable the Lua function
$\sin\alpha \cos {\gamma} \tan xy \sin{z} \cos^2x \cot ^ {(3!)} {\omega}$

%% make sure the function doesn't operate on terms with parens:
$\sin(\alpha) \cos (\gamma) \tan(xy) \sin(z) \cos ^2(x) \cot ^ {(3!)} (\omega)$

\medskip
\AddParensOff % disable the Lua function
$\sin\alpha \cos {\gamma} \tan xy \sin{z} \cos^2x \cot ^ {(3!)} {\omega}$
\end{document}
3
  • 1
    Well, we don't use LuaLaTeX at work (and I also don't know it very well), but I really appreciate the answer and I'll try to understand it for the future, where maybe I'll switch to it... ;) Any pdflatex-based answer is however still appreciated! :) – Mattia Bergomi Apr 14 '20 at 21:14
  • 1
    @MattiaBergomi - Until about two or three years ago, LuaLaTeX's output could be quite different from that of pdfLaTeX. That's no longer the case, fortunately. Assuming your TeX distribution is reasonably up to date, switching to LuaLaTeX should not involve much extra work -- if any! – Mico Apr 14 '20 at 21:21
  • Yes for my personal interest soon or later I'll try it seriously for sure (being able to use LaTeX with some sort of scripting language is definitely fascinating!), but for the moment the problem above concerns also a lot of colleagues who use pdlatex, as installed and configured at work, so I should still be able to solve it without such a fancy LuaLaTeX approach :) What drives me crazy is that the approach we are using works for ^ and _ alone, but I'm not able to modify the code with the \@ifnextchar to handle both cases... :S – Mattia Bergomi Apr 14 '20 at 22:05

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.