TransWikia.com

Is this a correct way to handle Boolean options?

TeX - LaTeX Asked on January 25, 2021

In trying to get a better understanding of kvoptions I have the following minimal example package and document. In the package, I construct a new macro command based on the logical states of three Boolean options. The presence of the Boolean false settings seems not to be a problem, but my question is whether or not this is an acceptable way to manipulate the options given that I get no errors or warnings.

The three options in this case are mutually exclusive and this seems to be the only way to ensure that. This is but a minimal example taken from a larger context in which there is already an existing command for changing the setup randomly within a document. No one could know that from this MWE (lack of context is a danger of using MWEs IHMO as exemplified here). I had already planned to document the requirement to specify only one option (again, not knowable from this MWE). I used a setup command because I have repeatedly been told that that is "the best way." So in the presence of correct functioning and absence of errors or warnings, how am I to know that something shouldn’t be done? If there is a better way to handle the Booleans (and I agree it looks somewhat awkward here), I’m open to learning it. That’s why I asked the question.

MWE package:

ProvidesPackage{tiny}[2021-01-05 v1.0 A tiny demo package]
RequirePackage{kvoptions}
SetupKeyvalOptions{%
  family=tiny,%
  prefix=tiny@,%
  setkeys=kvsetkeys%
}%

DeclareBoolOption[false]{optionone}
DeclareBoolOption[false]{optiontwo}
DeclareBoolOption[true]{optionthree}
ProcessKeyvalOptions{tiny}

newcommand*{tinysetup}{%
  kvsetkeys{tiny}%
}%

% This block doesn't quite work as expected with tinysetup{}
%AtBeginDocument{%
%  iftiny@optionone
%    newcommand*{momentum}[1]{#1,mathrm{kg}cdotmathrm{m}cdotmathrm{s}^{-1}}
%    optionone is activepar
%    tiny@optiontwofalse
%    optiontwo is not activepar
%    tiny@optionthreefalse
%    optionthree is not activepar
%  fi
%  iftiny@optiontwo
%    newcommand*{momentum}[1]{#1,mathrm{kg}cdotmathrm{m}/mathrm{s}}
%    tiny@optiononefalse
%    optionone is not activepar
%    optiontwo is activepar
%    tiny@optionthreefalse
%    optionthree is not activepar
%  fi  
%  iftiny@optionthree
%    newcommand*{momentum}[1]{#1,mathrm{N}cdotmathrm{s}}
%    tiny@optiononefalse
%    optionone is not activepar
%    tiny@optiontwofalse
%    optiontwo is not activepar
%    optionthree is activepar
%  fi
%}%

% This block seems to work as expected with tinysetup{}
AtBeginDocument{%
  newcommand*{momentum}[1]{#1,%
    iftiny@optionone
      tiny@optiontwofalse
      tiny@optionthreefalse
      mathrm{kg}cdotmathrm{m}cdotmathrm{s}^{-1}%
    fi
    iftiny@optiontwo
      tiny@optiononefalse
      tiny@optionthreefalse
      mathrm{kg}cdotmathrm{m}/mathrm{s}%
    fi
    iftiny@optionthree
      tiny@optiononefalse
      tiny@optiontwofalse
      mathrm{N}cdotmathrm{s}%
    fi
  }% % end of newcommand
}%

And here’s the MWE document that uses the package:

% !TEX TS-program = lualatexmk
% !TEX encoding = UTF-8 Unicode

documentclass{article}
usepackage{tiny}

begin{document}
Hello.

% should invoke optionthree
( momentum{3} )

% should invoke optionone
tinysetup{optionthree=false,optionone}
( momentum{3} )

% should invoke optiontwo
tinysetup{optionone=false,optiontwo}
( momentum{3} )

% should again invoke optionthree
tinysetup{optiontwo=false,optionthree}
( momentum{3} )

end{document}

2 Answers

Using another key=value package

Edit: I've changed this code example to also use the chardef variant (see below for a short explanation).

I'd drop kvoptions and use another key=value implementation which offers choice like keys for this. Viable options would be l3keys (with l3keys2e for package options support), pgfkeys (with pgfopts for package options support), expkv (with expkv-opt for package options support, and expkv-def for predefined key types), or options.

Since I'm the author of expkv, what follows is an example implementation using it.

Package:

ProvidesPackage{tiny}[2021-01-05 v1.0 A tiny demo package]

RequirePackage{expkv-opt,expkv-def}

ekvdefinekeys{tiny}
  {
    protected choice option =
      {
         one   = {chardeftiny@option0 }
        ,two   = {chardeftiny@option1 }
        ,three = {chardeftiny@option2 }
      }
    ,initial option = three
  }

ekvoProcessGlobalOptions{tiny}
ekvoProcessLocalOptions{tiny}

ekvsetdeftinysetup{tiny}

% This block seems to work as expected with tinysetup{}
AtBeginDocument{%
  newcommand*{momentum}[1]{#1,%
    ifcasetiny@option
      mathrm{kg}cdotmathrm{m}cdotmathrm{s}^{-1}%
    or
      mathrm{kg}cdotmathrm{m}/mathrm{s}%
    or
      mathrm{N}cdotmathrm{s}%
    fi
  }% % end of newcommand
}

Document:

documentclass{article}
usepackage{tiny}

begin{document}
Hello.

% should invoke optionthree
( momentum{3} )

% should invoke optionone
tinysetup{option=one}
( momentum{3} )

% should invoke optiontwo
tinysetup{option=two}
( momentum{3} )

% should again invoke optionthree
tinysetup{option=three}
( momentum{3} )

end{document}

Sticking to kvoptions

If you want to stick to kvoptions I'd drop the Bool options and use three Void options which directly set the Booleans (similar to the three choices in the code above). This way those keys don't take any values, but you get the correct behaviour of mutually exclusive options.

Package:

ProvidesPackage{tiny}[2021-01-05 v1.0 A tiny demo package]

RequirePackage{kvoptions}
SetupKeyvalOptions{%
  family=tiny,%
  prefix=tiny@,%
  setkeys=kvsetkeys
}

newififtiny@one
newififtiny@two
newififtiny@three
tiny@threetrue

DeclareVoidOption{optionone}  {tiny@onetruetiny@twofalsetiny@threefalse}
DeclareVoidOption{optiontwo}  {tiny@onefalsetiny@twotruetiny@threefalse}
DeclareVoidOption{optionthree}{tiny@onefalsetiny@twofalsetiny@threetrue}

ProcessKeyvalOptions{tiny}

newcommand*tinysetup{kvsetkeys{tiny}}

% This block seems to work as expected with tinysetup{}
AtBeginDocument{%
  newcommand*{momentum}[1]{#1,%
    iftiny@one
      mathrm{kg}cdotmathrm{m}cdotmathrm{s}^{-1}%
    fi
    iftiny@two
      mathrm{kg}cdotmathrm{m}/mathrm{s}%
    fi
    iftiny@three
      mathrm{N}cdotmathrm{s}%
    fi
  }% % end of newcommand
}%

Document:

documentclass{article}
usepackage{tiny}

begin{document}
Hello.

% should invoke optionthree
( momentum{3} )

% should invoke optionone
tinysetup{optionone}
( momentum{3} )

% should invoke optiontwo
tinysetup{optiontwo}
( momentum{3} )

% should again invoke optionthree
tinysetup{optionthree}
( momentum{3} )

end{document}

Alternative implementation

Instead of using three newifs, a perhaps more elegant implementation could be to use one macro that stores the respective choice as a number (the code uses kvoptions, the principle is applicable to the expkv example or any other key=value package as well):

Package:

ProvidesPackage{tiny}[2021-01-05 v1.0 A tiny demo package]

RequirePackage{kvoptions}
SetupKeyvalOptions{%
  family=tiny,%
  prefix=tiny@,%
  setkeys=kvsetkeys
}

% initial value
chardeftiny@option2

DeclareVoidOption{optionone}  {chardeftiny@option0 }
DeclareVoidOption{optiontwo}  {chardeftiny@option1 }
DeclareVoidOption{optionthree}{chardeftiny@option2 }

ProcessKeyvalOptions{tiny}

newcommand*tinysetup{kvsetkeys{tiny}}

% This block seems to work as expected with tinysetup{}
AtBeginDocument{%
  newcommand*{momentum}[1]{#1,%
    ifcasetiny@option
      mathrm{kg}cdotmathrm{m}cdotmathrm{s}^{-1}%
    or
      mathrm{kg}cdotmathrm{m}/mathrm{s}%
    or
      mathrm{N}cdotmathrm{s}%
    fi
  }% % end of newcommand
}

Document:

documentclass{article}
usepackage{tiny}

begin{document}
Hello.

% should invoke optionthree
( momentum{3} )

% should invoke optionone
tinysetup{optionone}
( momentum{3} )

% should invoke optiontwo
tinysetup{optiontwo}
( momentum{3} )

% should again invoke optionthree
tinysetup{optionthree}
( momentum{3} )

end{document}

Correct answer by Skillmon on January 25, 2021

This answer is just for fun (and because Skillmon didn't put something using expl3 :D). As you put in the description of your question, sometimes, it's hard to understand the context without having a long MWE, anyway, since expl3 came to stay, it's better to try to get used to it :), here is my best attempt.

documentclass{article}
begin{filecontents*}[overwrite]{tiny.sty}
RequirePackage{l3keys2e}
ProvidesExplPackage
    {tiny}
    {2021-01-05}
    {1.0}
    {A tiny demo package}

% Define a bool vars
bool_new:N l_tiny_option_one_bool
bool_new:N l_tiny_option_two_bool
bool_new:N l_tiny_option_three_bool

% Define a keys
keys_define:nn { tiny }
  {
    option .choice:,
    option / one   .code:n    = bool_set_true:N l_tiny_option_one_bool
                                bool_set_false:N l_tiny_option_two_bool
                                bool_set_false:N l_tiny_option_three_bool,
    option / two   .code:n    = bool_set_true:N l_tiny_option_two_bool
                                bool_set_false:N l_tiny_option_one_bool
                                bool_set_false:N l_tiny_option_three_bool,
    option / three .code:n    = bool_set_true:N l_tiny_option_three_bool
                                bool_set_false:N l_tiny_option_one_bool
                                bool_set_false:N l_tiny_option_two_bool,
    option         .initial:n = three,
    option .value_required:n  = true,
  }

% Process
ProcessKeysOptions { tiny }

% Setup
NewDocumentCommandtinysetup{ m }
  {
    keys_set:nn { tiny } { #1 }
  }

% Command
AtBeginDocument{
  NewDocumentCommandmomentum{ m }
    {
      group_begin:
        bool_if:NT l_tiny_option_one_bool
          {
            #1,mathrm{kg}cdotmathrm{m}cdotmathrm{s}^{-1}
          }
        bool_if:NT l_tiny_option_two_bool
          {
            #1,mathrm{kg}cdotmathrm{m}/mathrm{s}
          }
        bool_if:NT l_tiny_option_three_bool
          {
            #1,mathrm{N}cdotmathrm{s}
          }
      group_end:
    }
}
end{filecontents*}
usepackage{tiny} % default option=three
%usepackage[option=one]{tiny} % load option=one
begin{document}

Hello.

% should invoke option=three
( momentum{3} )

% should invoke option=one
tinysetup{option=one}
( momentum{3} )

% should invoke option=two
tinysetup{option=two}
( momentum{3} )

% should again invoke option=three
tinysetup{option=three}
( momentum{3} )

end{document}

Answered by Pablo González L on January 25, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP