6

I'm having some issues with expansion in precompiled l3keys. I'd like to define a command \NewThm{<name>}[<options>] that sets the displayed theorem name to be a titlecased version of the given <name> if no option name is given, and the value of name otherwise. Here is my attempt:

\documentclass{article}
\usepackage{amsthm}

\ExplSyntaxOn

\keys_define:nn { thmkeys/thm }
  {
    name .tl_set:N = \l_thmkeys_thm_name_tl,
  }

\tl_new:N \l_thmkeys_thm_defaultkeys_tl
\keys_precompile:nnN { thmkeys/thm }
  {
    name = \text_titlecase:n { \l_thmkeys_thm_envname_tl },
  }
  \l_thmkeys_thm_defaultkeys_tl

\NewDocumentCommand { \NewThm } { m O{} }
  { 
  % Store envname
    \tl_set:Nn \l_thmkeys_thm_envname_tl { #1 }
  % Set default keys
    \tl_use:N \l_thmkeys_thm_defaultkeys_tl
  % Set env-specific keys
    \keys_set:nn { thmkeys/thm } { #2 }
  % Call \newtheorem
    \thmkeys_thm_new:nV { #1 } \l_thmkeys_thm_name_tl
  }

\cs_new_eq:NN \thmkeys_thm_new:nn \newtheorem
\cs_generate_variant:Nn \thmkeys_thm_new:nn { nV }

\ExplSyntaxOff

\NewThm{theorem}
\NewThm{prop}[name=\textit{Proposition}]
\NewThm{lemma}
    
\begin{document}

\begin{theorem}
text
\end{theorem}

\begin{prop}
text
\end{prop}

\begin{lemma}
text
\end{lemma}

\end{document}

thms

I understand why it fails as is: \l_thmkeys_thm_envname_tl is not expanded during the call of \NewThm, so every theorem without a given name key will have the same displayed name.

Some thing's I've tried to fix it:

  1. Tell \newtheorem to use e-type expansion, as in \cs_generate_variant:Nn \thmkeys_thm_new:nn { ne }. This fixes the shown issue but fails if the name key contains things other than pure text, like the example name=\textit{proposition}.
  2. Manually expand the argument of \text_titlecase:nn. Adding \exp_args:Ne (or V or f) before \text_titlecase:n { \l_thmkeys_thm_envname_tl } has no effect.

How else can I expand \l_thmkeys_thm_envname_tl during the definition of \NewThm so that it doesn't break when it contains commands?

4
  • Why are you setting your envname variable instead of directly setting your name variable? This seems like an unnecessary indirection.
    – Skillmon
    Sep 29, 2023 at 20:37
  • In the less minimal code there are other keys and my goal was to keep them all in \keys_precompile where I can't say name = \text_titlecase:n { #1 }. There are supposedly speed benefits to precompiling but perhaps that's negated by the misdirection...
    – mbert
    Sep 29, 2023 at 21:04
  • @Skillmon Ah I misunderstood. You meant I should do \tl_set:Nn \l_thmkeys_thm_name_tl { \text_titlecase:n { #1 } }. That is clearly better. Please feel free to post as an answer
    – mbert
    Sep 29, 2023 at 21:09
  • 1
    yes originally I meant to simply do a \tl_set:Nn (well, actually a \tl_set:Ne/\tl_set:Nx -- better to pay the \text_titlecase:n overhead once even if unnecessary than on each usage later out in the document), but I think I added a slightly different answer, that might be more interesting, as it shows different ways to achieve this.
    – Skillmon
    Sep 29, 2023 at 21:16

1 Answer 1

5

You can use \text_expand:n to have a sort of expansion that should work for commands (there might be edge cases which aren't handled correctly though, \text_expand:n isn't the most simple macro).

But honestly, I think it's easier to set the name macro in an different way than via a variable in a frozen/precompiled kv-list.

Anyways, first the code that shows your current way, but with \text_expand:n:

\documentclass{article}
\usepackage{amsthm}

\ExplSyntaxOn

\keys_define:nn { thmkeys/thm }
  {
    name .tl_set:N = \l_thmkeys_thm_name_tl,
  }

\tl_new:N \l_thmkeys_thm_defaultkeys_tl
\keys_precompile:nnN { thmkeys/thm }
  {
    name = \text_titlecase:n { \l_thmkeys_thm_envname_tl },
  }
  \l_thmkeys_thm_defaultkeys_tl

\NewDocumentCommand { \NewThm } { m O{} }
  { 
  % Store envname
    \tl_set:Nn \l_thmkeys_thm_envname_tl { #1 }
  % Set default keys
    \tl_use:N \l_thmkeys_thm_defaultkeys_tl
  % Set env-specific keys
    \keys_set:nn { thmkeys/thm } { #2 }
  % Call \newtheorem
    \thmkeys_thm_new:ne { #1 } { \text_expand:n { \l_thmkeys_thm_name_tl } }
  }

\cs_new_eq:NN \thmkeys_thm_new:nn \newtheorem
\cs_generate_variant:Nn \thmkeys_thm_new:nn { ne }

\ExplSyntaxOff

\NewThm{theorem}
\NewThm{prop}[name=\textit{Proposition}]
\NewThm{lemma}
    
\begin{document}

\begin{theorem}
text
\end{theorem}

\begin{prop}
text
\end{prop}

\begin{lemma}
text
\end{lemma}

\end{document}

A variant that doesn't use a precompiled kv-list but instead sets the default (at least for name) inside of \keys_set:nn:

\documentclass{article}
\usepackage{amsthm}

\ExplSyntaxOn

\keys_define:nn { thmkeys/thm }
  {
    name .tl_set:N = \l_thmkeys_thm_name_tl,
  }

\NewDocumentCommand { \NewThm } { m O{} }
  { 
  % Store envname
    \tl_set:Nn \l_thmkeys_thm_envname_tl { #1 }
  % Set env-specific keys
    \exp_args:Nne \keys_set:nn { thmkeys/thm }
      { name = { \text_titlecase:n {#1} }, \exp_not:n {#2} }
  % Call \newtheorem
    \thmkeys_thm_new:nV { #1 } { \l_thmkeys_thm_name_tl }
  }

\cs_new_eq:NN \thmkeys_thm_new:nn \newtheorem
\cs_generate_variant:Nn \thmkeys_thm_new:nn { nV }

\ExplSyntaxOff

\NewThm{theorem}
\NewThm{prop}[name=\textit{Proposition}]
\NewThm{lemma}
    
\begin{document}

\begin{theorem}
text
\end{theorem}

\begin{prop}
text
\end{prop}

\begin{lemma}
text
\end{lemma}

\end{document}

Another version using expkv1 instead of l3keys. expkv also features precompiling key lists, but inside of expkv the precompilation works slightly different. Here the key list isn't fixed (well, the key list is, but not the values), but instead \ekvcompile works like \cs_set:Npn with key-value processing intervined. This way, you can precompile your keys, but still give different values to the individual keys via macro arguments.

In expkv-def (a kv-list based frontend to define keys in expkv) the equivalent of .tl_set:N is named store. Note that expkv uses space-delimited prefixes instead of .-separated postfixes to denote the key types.

The drawback is that you mix in non-L3-syntax into your code, as exkpv isn't programmed in L3, and has no frontend for it.

1Disclaimer: I'm the author of expkv

\documentclass{article}
\usepackage{amsthm}

\usepackage{expkv-def}

\ExplSyntaxOn

\ekvdefinekeys { thmkeys/thm }
  {
    store~ name = \l_thmkeys_thm_name_tl
  }
\ekvcompile \__thmkeys_defaults:n #1 { thmkeys/thm } { name = {#1} }
\cs_generate_variant:Nn \__thmkeys_defaults:n { e }

\NewDocumentCommand { \NewThm } { m O{} }
  { 
  % Store envname
    \tl_set:Nn \l_thmkeys_thm_envname_tl { #1 }
  % Set defaults
    \__thmkeys_defaults:e { \text_titlecase:n { \l_thmkeys_thm_envname_tl } }
  % Set env-specific keys
    \ekvset { thmkeys/thm } { #2 }
  % Call \newtheorem
    \thmkeys_thm_new:nV { #1 } { \l_thmkeys_thm_name_tl }
  }

\cs_new_eq:NN \thmkeys_thm_new:nn \newtheorem
\cs_generate_variant:Nn \thmkeys_thm_new:nn { nV }

\ExplSyntaxOff

\NewThm{theorem}
\NewThm{prop}[name=\textit{Proposition}]
\NewThm{lemma}
    
\begin{document}

\begin{theorem}
text
\end{theorem}

\begin{prop}
text
\end{prop}

\begin{lemma}
text
\end{lemma}

\end{document}

Yet another pure L3 answer could be to, instead of setting your name key directly to your envname, set it to \q_no_value, and later check whether it's \q_no_value, and if so, use your envname.

This variant has the additional advantage that \text_titlecase:n is only used if really necessary (and \text_titlecase:n is comparatively slow).

\documentclass{article}
\usepackage{amsthm}

\ExplSyntaxOn

\keys_define:nn { thmkeys/thm }
  {
    name .tl_set:N = \l_thmkeys_thm_name_tl,
  }

\tl_new:N \l_thmkeys_thm_defaultkeys_tl
\keys_precompile:nnN { thmkeys/thm }
  {
    name = \q_no_value,
  }
  \l_thmkeys_thm_defaultkeys_tl

\NewDocumentCommand { \NewThm } { m O{} }
  { 
  % Store envname
    \tl_set:Nn \l_thmkeys_thm_envname_tl { #1 }
  % Set default keys
    \tl_use:N \l_thmkeys_thm_defaultkeys_tl
  % Set env-specific keys
    \keys_set:nn { thmkeys/thm } { #2 }
  % Call \newtheorem
    \quark_if_no_value:NTF \l_thmkeys_thm_name_tl
      {
        \thmkeys_thm_new:ne {#1}
          { \text_titlecase:n { \l_thmkeys_thm_envname_tl } }
      }
      { \thmkeys_thm_new:nV {#1} \l_thmkeys_thm_name_tl }
  }

\cs_new_eq:NN \thmkeys_thm_new:nn \newtheorem
\cs_generate_variant:Nn \thmkeys_thm_new:nn { nV, ne }

\ExplSyntaxOff

\NewThm{theorem}
\NewThm{prop}[name=\textit{Proposition}]
\NewThm{lemma}
    
\begin{document}

\begin{theorem}
text
\end{theorem}

\begin{prop}
text
\end{prop}

\begin{lemma}
text
\end{lemma}

\end{document}
2
  • Thanks, this is very helpful. General question: is this method of setting a key to \q_no_value and branching with \quark_if_no_value:NTF faster than setting the key to be empty and branching with \tl_if_empty:NTF?
    – mbert
    Sep 29, 2023 at 21:32
  • 1
    @mbert they are equally fast (or at least immeasurably different). Both are equal to a simple \ifx comparison, the difference is to what this is compared. But the \q_no_value is the better flag for no name given, as \q_no_value is really unlikely to be user input.
    – Skillmon
    Sep 29, 2023 at 21:35

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .