olsak/OpTeX

Add equivalent to `\providecommand` from LaTeX

robertbachmann opened this issue · 5 comments

LaTeX has \providecommand{\NAME}{...}, that is a shorthand for:

\unless\ifcsname NAME\endcsname \newcommand{\NAME}{...} \fi

(see https://latexref.xyz/_005cprovidecommand.html)

In the past I've used this pattern in OpTeX:

\isdefined{foo}\iffalse
	\def\foo{...}
\fi

However since I define multiple macros that way (use case explained below), it got way to verbose
and I decided to write my own macro \softdef and \softsdef:

\def\softdef#1{\ea\ifcsname\csstring #1\endcsname%
  \afterfi{\ea\def\csname softdef:dummy\endcsname}%
  \else\afterfi{\def#1}\fi}
\def\softsdef#1{\ifcsname #1\endcsname%
  \afterfi{\ea\def\csname softdef:dummy\endcsname}%
  \else\afterfi{\ea\def\csname #1\endcsname}\fi}

It's written so that for example \long\protected\softdef\foo would also do the right thing.

Notes:

  • I never had a concrete use case for a "soft" version of \let so I never looked
    into that.
  • I experimented with different names. Initially I had \providedef and \providesdef.
    I'm currently prefering "soft" since it makes more sense to me, but don't care
    strongly about the name of the macros.

My proposal:

  • a) Include the macros in OpTeX's base format
  • b) Or alternatively, add them as loadable tricks?

I think I would prefer base since I think autoloading might get confusing:

This case would work:

	\softdef\foo{...}

This case would most likely not work without explicit '\loadtrick\softdef' beforehand:

	\long\softdef\qux{...}

However if you don't want to have these macros in the base format, it's also
fine. I can do '\loadtrick\softdef' beforehand.

Please let me know, so I can prepare a pull-request for option a or b.


Side note: My use case

For the pandoc optex writer I'm using some helper macros.
Backgroun: When converting **xy**, pandoc does

  • uses logical <strong>xy</strong> for HTML
  • uses physical formatting \textbf{xy} for LaTeX (because the maintainers don't want to use custom macros)

So for HTML the user can easily customize with CSS:

strong { font-weight: normal;  color: red; }

For LaTeX he would need to overwrite \textbf which might cause some unwanted side effects.

For the OpTeX writer I decided¹ against {\bf xy} instead I produce \strong{xy} and provide a default
defintion for \strong at the top of the generated document/document fragment².

\softdef\strong#1{\begingroup\bf #1\endgroup}
\regmacro{}{}{\let\strong=\useit}

If the user wants a different style he can simply:

    \def \strong#1{\begingroup\Red\it #1\endgroup}

\strong is just one example. Another example would be [Some marked text]{.mark} (<mark> html element) or * * * (<hr>)

¹ can be added as toggle-able option if someone really needs it
² fragment: Pandoc can generate standalone documents or partial documents (fragments). For TeX these fragments would then be usually included via \input.

olsak commented

Maybe, it should be better to create a "prefix" for more general usage, like \newpublic is. For example its name can be \newonly and we can use \newonly\def\foo, \newonly\edef\foo, \newonly\mathchardef\foo, \newonly\newcount\foo and we can use \newonly{\long\def}\foo . This prefix can be created as loadable trick.

Yes, that would work as well!
The only thing I would need that \newonly would work with both \def and \sdef.

I think the following should work:

<h2><a name="newonly"></a>Only define a macro if it is not already defined</h2>

<p CLASS="rmarg">
autoload:<br>\newonly
</p>

<p>
LaTeX has <code>\providecommand{\NAME}{...}</code>
that only defines <code>\NAME</code> if it was not already defined.
We can achieve this in OpTeX by wrapping <code>\def\NAME{...}</code> inside
<code>\isdefined{\NAME}\iffalse ...\fi</code>.
However, this will get verbose if we need to define many macros in this manner.
Therefore, we implement a similar functionality as a prefix macro <code>\newonly</code>.
</p>

<p>Usage examples:</p>
<pre>
\newonly\def\mymacro#1{...}
\newonly\let\myfont=\bf  \newonly\let\myfont\bf
\newonly\slet{myfont}{bf}
\newonly{\protected\long\def}\foo#1{...}
\newonly\newtoks\mytoks
</pre>

<p>The implementation:</p>
<pre>
\def\newonly#1#2{\begingroup%
   \edef\tmpA{\_noprefix{#2}}%
   \edef\tmpB{\string #2}%
   \ea\ifcsname\tmpA\endcsname%
   \ifx\tmpA\tmpB% #2 has no backslash
   \gdef\newonlyA{#1{newonlyB}}\else%
   \gdef\newonlyA{#1\newonlyB}\fi%
   \else%
   \ifx\tmpA\tmpB%
   \gdef\newonlyA{#1{#2}}\else%
   \gdef\newonlyA{#1#2}\fi%
   \fi\endgroup\newonlyA}
</pre>

<p CLASS=datum>(0129) -- Robert Bachmann 2024-02-11<p>

edit: I can prepare a PR in the evening or on monday

olsak commented
  • A minor improvements: \ea\endgroup\newonlyA and \def instead \gdef (4x).
  • Why use \_noprefix and no single \csstring? I mean that nobody needs to use \newonly\def\_foo. Moreower it is bad because \foo is checked but \_foo is redefined.
  • The name \newonly for this prefix was my first idea. If you can find better name, use it.
  • Maybe , abbreviations \def\softdef{\newonly\def}, \def\softsdef{\newonly\sdef} should be mentioned in the OpTeX trick too.

Thanks for your feedback.

It's a bit hard to find a good name: things I like so far \onlynew and \onlyifnew:


LaTeX has \providecommand{\NAME}{...} that only defines \NAME if it
was not already defined. We can achieve this in OpTeX by wrapping
\def\NAME{...} inside \isdefined{\NAME}\iffalse ...\fi. However,
this will get verbose if we need to define many macros in this manner.
Therefore, we implement a similar functionality as a prefix macro
\onlyifnew.

Usage examples:

\onlyifnew\def\mymacro#1{...}
\onlyifnew\let\myfont=\bf  \onlyifnew\let\myfont\bf
\onlyifnew\slet{myfont}{bf}
\onlyifnew\newtoks\mytoks
\onlyifnew{\protected\long\def}\foo#1{...}

In the last case, it could be useful to define a shortcut such as:

\def\mylpdef{\onlyifnew{\protected\long\def}} 
\mylpdef\foo#1{...}

The implementation:

\def\onlyifnew#1#2{\begingroup% 
	\edef\tmpA{\csstring #2}%
	\edef\tmpB{\string #2}%
	\ea\ifcsname\tmpA\endcsname% 
	\ifx\tmpA\tmpB% #2 has no backslash
	\def\onlyifnewA{#1{onlyifnewB}}\else%
	\def\onlyifnewA{#1\onlyifnewB}\fi%
	\else%
	\ifx\tmpA\tmpB%
	\def\onlyifnewA{#1{#2}}\else%
	\def\onlyifnewA{#1#2}\fi%
	\fi\ea\endgroup\onlyifnewA}
olsak commented

OK, would you prepare the pull request?