1

I have multiple pieces of redundant LaTeX3 code that creates new conditionals:

\bool_if:nT
  { \cs_if_free_p:N \mypkg_if_package_loaded_p:n &&
    \cs_if_free_p:N \mypkg_if_package_loaded_T:n &&
    \cs_if_free_p:N \mypkg_if_package_loaded_F:n &&
    \cs_if_free_p:N \mypkg_if_package_loaded_TF:n }
  {
    \prg_new_conditional:Nnn \mypkg_if_package_loaded:n { p, T, F, TF }
      {
        \use:c { @ifpackageloaded } { #1 }
          { \prg_return_true: }
          { \prg_return_false: }
      }
  }

I want to replace the first code with something more concise, like the following:

\cs_new_protected:Npn \mypkg_def_if:Nn #1#2
  {
    \clist_new:N \l_mypkg_cond_clist
    \clist_map_inline:nn { p, T, F, TF }
      {
        \bool_if:nT { \cs_if_free_p:N #1:##1 }
          { \clist_put_right:Nn \l_mypkg_cond_clist { #1 } }
      }
    \prg_new_conditional:Nnn #2:n { \l_mypkg_cond_clist }
      {
        \use:c { #2 } { ##1 }
          { \prg_return_true: }
          { \prg_return_false: }
      }
  }

\mypkg_def_if:Nn \mypkg_if_package_loaded { @ifpackageloaded }
\mypkg_def_if:Nn \mypkg_if_package_with { @ifpackagewith }
\mypkg_def_if:Nn \mypkg_if_class_loaded { @ifclassloaded }
\mypkg_def_if:Nn \mypkg_if_class_with { @ifclasswith }

To solve it I'm trying to follow this structure:

  1. Create a new clistA
  2. Iterate over {p, T, F, TF}
  3. For each element, check if function + element is available with \cs_if_free
  4. If available, add it to clistB
  5. Create a conditional with arg1 and clistB
  6. Use arg2 as a control sequence
  7. Attempt to disambiguate the arguments

Any feedback appreciated! 🤍

4
  • 3
    I'm not sure what \cs_if_free:… would be needed for, as you seem to be writing package code and you are in charge of what's defined.
    – egreg
    Commented May 22, 2023 at 12:20
  • 1
    That's a good point! Since I'll use it as a helper function I'd want to safeguard myself from defining already existing conditionals.
    – Neveda
    Commented May 22, 2023 at 12:58
  • 1
    @Neveda don't. It's your package and you should use a unique module-prefix for it (mypgk in your MWE). If you use \prg_new_conditional:Npnn you'll get an error that the conditional is already defined, and if it is you can just remove the duplicate definition from your sources.
    – Skillmon
    Commented May 22, 2023 at 16:32
  • I see and understand your concerns. The code is already in place and serves its purpose well, acting the same way \ProvideDocumentCommand does. I'm looking for a way to make it shorter and DRY.
    – Neveda
    Commented May 23, 2023 at 13:27

2 Answers 2

4

I can't see the need for \cs_if_free:NTF. You're developing a package and if, by mistake, you try redefining an already defined conditional in your own namespace, you'll be welcome with an error message that says so. In this case just remove the faulty piece of code and go on.

This is the reason why a prefix specific to a package is recommended: you're, the package author, are completely responsible for the functions and variables using that prefix.

Actually, with your \bool_if:nT you're sweeping the problem under the carpet. Suppose you have already defined \mypkg_if_foo:nTF in your big package and at some later time you forget having done so and deem foo good for some other conditional. If you use your \bool_if:nT strategy, your new conditional will not be defined and the older function will still be used at each call of \mypkg_if_foo:nTF. Are you sure you wouldn't prefer an error message, so you can realize the mistake and choose a new name? I would.

Using \bool_if:nTF and a warning in the “false” branch would help, but I see no advantage.

Last, but not least, definitions of similar functions should not be scattered among the package, but kept near to each other, for easier maintenance. Thus you'll have a section

% define conditionals emulating the LaTeX kernel pseudoconditionals
\prg_new_conditional:Nnn \mypkg_if_package_loaded:n { p, T, F, TF }
 {
  \use:c { @ifpackageloaded } { #1 } { \prg_return_true: } { \prg_return_false: }
 }

Now we can write syntactic sugar for defining such conditional “at once”.

% define conditionals emulating the LaTeX kernel pseudoconditionals
\cs_new_protected:Nn \__mypkg_define_kernel_conditional:Nn
 {
  \prg_new_conditional:Nnn #1 { p,T,F,TF }
   {
    \use:c { #2 } { \prg_return_true: } { \prg_return_false: }
   }
 }
\__mypkg_define_kernel_conditional:Nn \mypkg_if_package_loaded:n { @ifpackageloaded }
\__mypkg_define_kernel_conditional:Nn \mypkg_if_package_with:n { @ifpackagewith }
\__mypkg_define_kernel_conditional:Nn \mypkg_if_class_loaded:n { @ifclassloaded }
\__mypkg_define_kernel_conditional:Nn \mypkg_if_class_with:n { @ifclasswith }

If you really want to use your \bool_if:nT strategy, then

\cs_new_protected:Nn \__mypkg_define_kernel_conditional:nn
 {
  \bool_if:nT
   {
    \cs_if_free_p:c {mypkg_if_#1_p:n} &&
    \cs_if_free_p:c {mypkg_if_#1:nT &&
    \cs_if_free_p:c {mypkg_if_#1:nF &&
    \cs_if_free_p:c {mypkg_if_#1:nTF
   }
   {
    \prg_new_conditional:cnn {mypkg_if_#1:n} { p,T,F,TF }
     {
      \use:c { #2 } { \prg_return_true: } { \prg_return_false: }
     }
   }
 }
\__mypkg_define_kernel_conditional:nn {package_loaded} { @ifpackageloaded }
\__mypkg_define_kernel_conditional:nn {package_with} { @ifpackagewith }
\__mypkg_define_kernel_conditional:nn {class_loaded} { @ifclassloaded }
\__mypkg_define_kernel_conditional:nn {class_with} { @ifclasswith }
1
  • I like this one the most, thank you!
    – Neveda
    Commented Jun 15, 2023 at 20:25
3

I still think that your package is your namespace, hence your responsibility. As long as this isn't used via a user-interface to set up some internal auxiliaries I'd not advise using it and instead simply do a \cs_new_eq:Nc \nevada_if_package_loaded:nTF { @ifpackageloaded } to get the conditional if you absolutely need a package name for a 2e-macro.

The following might be more complicated than it has to be, but does what you want:

\documentclass{article}

\ExplSyntaxOn
\clist_new:N \l__nevada_variants_clist
\tl_new:N \l__nevada_cs_parts_tl
\tl_new:N \l__nevada_arg_forwarded_tl
\str_new:N \l__nevada_cs_base_str
\str_new:N \l__nevada_cs_args_str
\int_new:N \l__nevada_arg_count_int
\msg_new:nnn { nevada } { missing-colon }
  { Function~ name~ #1~ misses~ a~ colon. }
\cs_new_protected:Npn \__nevada_split_cs:N #1
  {
    \tl_set:Nx \l__nevada_cs_parts_tl { \cs_split_function:N #1 }
    \bool_if:nF { \exp_last_unbraced:No \use_iii:nnn \l__nevada_cs_parts_tl }
      {
        \msg_error:nnn { nevada } { missing-colon } {#1}
        \prg_break:
      }
    \str_set:Nx \l__nevada_cs_base_str
      { \exp_last_unbraced:No \use_i:nnn  \l__nevada_cs_parts_tl }
    \str_set:Nx \l__nevada_cs_args_str
      { \exp_last_unbraced:No \use_ii:nnn \l__nevada_cs_parts_tl }
  }
\cs_new:Npn \__nevada_cs_base:
  { \exp_last_unbraced:No \use_i:nnn \l__nevada_cs_parts_tl }
\cs_new_protected:Npn \__nevada_provide_conditional:NNnn #1#2#3#4
  {
    \__nevada_split_cs:N #2
    \clist_clear:N \l__nevada_variants_clist
    \clist_map_inline:nn {#3}
      {
        \cs_if_exist:cF
          {
            \l__nevada_cs_base_str
            \str_if_eq:nnT {##1} { p } { _p }
            : \l__nevada_cs_args_str
            \str_if_eq:nnF {##1} { p } { \tl_to_str:n {##1} }
          }
          { \clist_put_right:Nn \l__nevada_variants_clist {##1} }
      }
    \clist_if_empty:NF \l__nevada_variants_clist
      {
        \exp_args:NNe #1 #2
          { \clist_use:Nn \l__nevada_variants_clist { , } }
          {#4}
      }
    \prg_break_point:
  }
\cs_new_protected:Npn \nevada_provide_conditional:Nnn
  { \__nevada_provide_conditional:NNnn \prg_new_conditional:Nnn }
\cs_new_protected:Npn \nevada_provide_protected_conditional:Nnn
  { \__nevada_provide_conditional:NNnn \prg_new_protected_conditional:Nnn }

\cs_new_protected:Npn \nevada_provide_all_conditionals:Nn #1
  { \nevada_provide_conditional:Nnn #1 { TF, T, F, p } }
\cs_new_protected:Npn \nevada_provide_all_protected_conditionals:Nn #1
  { \nevada_provide_protected_conditional:Nnn #1 { TF, T, F } }
\cs_new_protected:Npn \__nevada_provide_conditional_alias:NNN #1#2#3
  {
    \__nevada_split_cs:N #2
    \int_set:Nn \l__nevada_arg_count_int { \str_count:N \l__nevada_cs_args_str }
    \tl_clear:N \l__nevada_arg_forwarded_tl
    \int_step_inline:nn { \l__nevada_arg_count_int }
      { \tl_put_right:Nn \l__nevada_arg_forwarded_tl { {######1} } }
    \exp_args:NNe #1 #2
      {
        \exp_not:N #3 \l__nevada_arg_forwarded_tl
          \exp_not:N \prg_return_true:
          \exp_not:N \prg_return_false:
      }
    \prg_break_point:
  }
\cs_new_protected:Npn \nevada_provide_conditional_alias:NN
  {
    \__nevada_provide_conditional_alias:NNN \nevada_provide_all_conditionals:Nn
  }
\cs_new_protected:Npn \nevada_provide_protected_conditional_alias:NN
  {
    \__nevada_provide_conditional_alias:NNN
      \nevada_provide_all_protected_conditionals:Nn
  }
\cs_generate_variant:Nn \nevada_provide_protected_conditional_alias:NN { Nc }

\nevada_provide_protected_conditional_alias:Nc
  \nevada_if_package_loaded:n
  { @ifpackageloaded }

\ExplSyntaxOff

\usepackage{graphicx}

\begin{document}
\texttt{graphicx} is
\ExplSyntaxOn
\nevada_if_package_loaded:nF {graphicx} { not~ }
\ExplSyntaxOff
loaded.

You
\ExplSyntaxOn
\nevada_if_package_loaded:nTF {xcolor} { did } { should } ~
\ExplSyntaxOff
load \texttt{xcolor}.
\end{document}
1
  • This one is amazing! It has a lot of work into it, thank you lots!
    – Neveda
    Commented Jun 15, 2023 at 20:26

You must log in to answer this question.

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