2

I think I finally have a reasonable understanding of pgfkeys, but initializing a key with or without the .initial handler is giving me trouble. I do understand the distinction between .default and .initial in that the former specifies a value when none is given for a key and the latter specifies a value otherwise (that is my understanding, anyway). The MWE contains four example cases.

The first one seems to work correctly, except that trying to initialize paintwhat and paintshade with .initial gives an error about an undefined control sequence. The second example seems to work correctly, but again trying to initialize units with .initial while .default seems to work perfectly. In the third example, I understand that .initial doesn't work with subkeys and I think that's why I can't use it in this example. How, then, can I initialize sunits? Finally, in the fourth example, when I initialize zunits LAST inside \pgfkeys{...} the initial use of \picktheunits works as expected, but the final use does not.

Ultimately, my question is: what causes these difficulties in initializing a key? Is is something trivial I'm overlooking, or is it something subtle?

(In case anyone is wondering, this example with units is my prototypical test case for testing keys and values.)

Here is the MWE:

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

\documentclass{article}
\usepackage{pgfkeys}

\begin{document}

A command about painting things.\par
%\newcommand*{\paintit}{}   % this appears to be optional
%\newcommand*{paintshadw}{} % this appears to be optional
\pgfkeys{%
  /paintthe/.is family, /paintthe,
  paintwhat/.store in=\paintit,
  paintwhat/.default=building, % used when just paintwhat is given
  paintwhat = barn,            % initial value of paintwhat; can be overwritten
  paintshade/.store in=\paintshade,
  paintshade/.default=light,   % used when just paintshade is given
  paintshade = light,          % initial value of paintshade; can be overwritten
}%

%  version with shade
\newcommand*{\paintthecolor}[2][]{%
  \begingroup % make key values local to this instance
    \pgfkeys{/paintthe,#1}
    Paint the \paintit\ \paintshade\ #2.
  \endgroup
}%

\paintthecolor{green}\par
\paintthecolor[paintwhat]{green}\par
\paintthecolor[paintwhat=house,paintshade=dark]{red}\par
\paintthecolor[paintwhat=car,paintshade=medium]{blue}\par
\paintthecolor{gray}\par

% These commands will already be defined, albeit not as they are here.
\newcommand*{\perpusederivedunits}{You'll get derived units.}
\newcommand*{\perpusebaseunits}{You'll get base units.}
\newcommand*{\perpusealternateunits}{You'll get alternate units.}

A command about selecting default units. Seems to work correctly.\par
\pgfkeys{%
  /chooseunits/.is family, /chooseunits,
  units/.store in=\chosenunits,
  units/.default=alternate,
  units=derived, % .initial doesn't work here
}%

\newcommand*{\choosetheunits}[1][]{%
  \begingroup
    \pgfkeys{/chooseunits,#1}
    \csname perpuse\chosenunits units\endcsname % this builds the command that selects the units
  \endgroup
}%
\choosetheunits\par % works correctly
\choosetheunits[units=base]\par
\choosetheunits[units]\par
\choosetheunits[units=derived]\par
\choosetheunits[units=alternate]\par
\choosetheunits[units=derived]\par
\choosetheunits[units]\par
\choosetheunits\par % works correctly

A different approach using \texttt{.is choice} handler. Can't initialize.\par
\pgfkeys{%
  /selectunits/.is family, /selectunits,% /.cd,
  sunits/.is choice,
  sunits/.initial={\perpusederivedunits}, % .initial doesn't work with .is choice
  sunits/.default=alternate,
  sunits/base/.code={\perpusebaseunits},
  sunits/derived/.code={\perpusederivedunits},
  sunits/alternate/.code={\perpusealternateunits},
}%
\newcommand*{\settheunits}[1][]{%
  \begingroup
    \pgfkeys{/selectunits,#1}
  \endgroup
}%
\settheunits\par % doesn't work since .initial doesn't apply to .is choice
\settheunits[sunits=base]\par
\settheunits[sunits]\par
\settheunits[sunits=derived]\par
\settheunits[sunits=alternate]\par
\settheunits[sunits=derived]\par
\settheunits[sunits]\par
\settheunits\par % doesn't work since .initial doesn't apply to .is choice

Another approach to the unit problem. Partially works.\par
\pgfkeys{%
  /pickunits/.is family, /pickunits,
  %zunits=derived, % throws unknown key error when placed here
  zunits/.default=alternate,
  zunits/.code={\csname perpuse#1units\endcsname},
  zunits=derived, % works partially when placed here
}%
\newcommand*{\picktheunits}[1][]{%
  \begingroup
    \pgfkeys{/pickunits,#1}
  \endgroup
}%
\picktheunits\par % works correctly
\picktheunits[zunits=base]\par
\picktheunits[zunits]\par
\picktheunits[zunits=derived]\par
\picktheunits[zunits=alternate]\par
\picktheunits[zunits=derived]\par
\picktheunits[zunits]\par
\picktheunits\par % does not work
\end{document}
1
  • 2
    IIRC, the .initial handler stores the value inside the key itself, so after \pgfkeys{/foo/.initial=bar} you can use \pgfkeys{/foo=baz} to change it's value (as long as no .code is associated with it), and retrieve its value either with .get or \pgfkeysvalueof{/foo}. As .store is (more or less) the same as .code={\def<macro>{#1}}, the same remarks as mentioned in the documentation for the .initial handler (currently page 989) applies.
    – Skillmon
    Jan 17, 2021 at 8:43

2 Answers 2

4

The .initial key handler is used to store a value inside the key in pgfkey's name space. If a key has no other handler associated other than the .initial handler, it becomes similar to a .store in key, in that it'll store new values inside the key when used. The stored values can then be retrieved either via the .get handler, or with \pgfkeysvalueof.

When you also use .code the key will continue to store the last value stored (either from before adding .code, or from a later use of .initial), but now the .code takes precedence when the key is used without a handler. So when you now do \pgfkeys{/foo=abc} it'll not store abc, but execute .code for abc.

Any (or most?) of the other handlers are internally using .code, so as soon as you add another handler to a key it loses the functionality to store values inside key itself from normal use.

However in your case, you never had the intention to use the storage functionality of pgfkeys, but use handlers, such as .store in. So you don't use .initial inside pgfkeys for initial values, as you would with some other key=value packages (like l3keys or expkv-def). Instead the correct way to set an initial value for a key using a handler like .choice or .store in is to just use the key, e.g., with \pgfkeys{/foo=bar}.

Btw. your undefined control sequence stems from the fact that .store in doesn't initialize the macros used, so after \pgfkeys{/foo/.store in=\foo} the macro \foo isn't defined yet, it'll just be defined after you use the key for the first time.


Another method to define an initial value for a macro defined via .store in would be to use \newcommand*:

\newcommand*\foo{bar}
\pgfkeys{/foo/.store in=\foo,/foo/.default=baz}

would add a key /foo, that stores inside \foo, has the initial value bar and the default baz.

6
  • 1
    Is it correct to say that unless I somehow specify an initial value for a key and I'm using .code, I MUST specify THAT key when calling a command so the .default value will be used? In other words, the key must ALWAYS be provided and shouldn't be omitted. I hope that question makes sense. Jan 17, 2021 at 20:13
  • 2
    @LaTeXereXeTaL I'm not entirely sure I understood your question, so I answer what I think is: that really depends on your code. In case of your .store in key, you get an error because it was never used (actually because the macro you tried to access was never defined). If you define a key and want to set an initial value (so want the code to behave like the key was used once with that value), you can do so by using the key once with that value. The .default value is the value that will be used if no value is specified, so with \pgfkeys{/foo}, that's not the same as an initial value.
    – Skillmon
    Jan 17, 2021 at 21:34
  • 1
    To clarify, if I want to call a command without specifying any keys and having a value to use in such cases. I think that's what you addressed. Please correct me if I'm wrong! Jan 17, 2021 at 23:32
  • 2
    What .default does is that after \pgfkeys{/foo/.default=baz} every call \pgfkeys{/foo} is the same as \pgfkeys{/foo=baz}. That's not the same as \pgfkeys{}! So if you write your own macro, and you want to use .store in, you'd define the key \pgfkeys{/foo/.store in=\foo}, then set an initial value \pgfkeys{/foo=bar}, and maybe a default \pgfkeys{/foo/.default=baz}. Of course the order of initial and default value doesn't matter, and you can do all this in one \pgfkeys call. If the initial value should be the default: \pgfkeys{/foo/.store in=\foo,/foo/.default=bar,/foo}.
    – Skillmon
    Jan 18, 2021 at 0:06
  • 2
    @LaTeXereXeTaL And yes, an initial value is the value the key should have from the beginning, so if the user doesn't use the key him/herself. The default value is just the value used as a fallback if the key is used but no value was specified in the input. And the .initial handler is strange and unintuitive and seldomly what you want (but there are people making really good use of it...).
    – Skillmon
    Jan 18, 2021 at 0:09
1

After more experimentation, I finally found what I think is a good solution using \NewDocumentCommand. There are two \pfgkeys{...} blocks, one that uses .is choice and one that does not. Obviously only one of the two can be used at a time, but both give the same results and the new command's syntax works exactly as I expect it to. The idea is to test for the presence of the optional argument containing keys, or the absence thereof, and execute the appropriate code. I want to eventually use \NewDocumentCommand as much as possible so this seems to be a nice solution. I assume someone will let me know if I've committed any atrocities.

EDIT: I added a sanity check on the key value.

Here is the MWE:

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

\documentclass{article}
\usepackage{pgfkeys}

\begin{document}

\newcommand*{\perpusebaseunits}{You'll get base units.}
\newcommand*{\perpusederivedunits}{You'll get derived units.}
\newcommand*{\perpusealternateunits}{You'll get alternate units.}

% This block does not use .is choice
\pgfkeys{%
  /selectunits/.is family, /selectunits,%
  sunits/.initial=alternate,%
  sunits/.default=derived,%
  sunits/.code={%
    \ifcsname perpuse#1units\endcsname
      \csname perpuse#1units\endcsname
    \else
      \GenericError{}
        {\MessageBreak settheunits: Illegal key value}
        {Key 'sunits' can only be one of base, derived, or alternate.}
        {Read the documentation for help.}
    \fi
  }%
  %sunits/.code={\csname perpuse#1units\endcsname},%
}%

% This block uses .is choice
%\pgfkeys{%
%  /selectunits/.is family, /selectunits,%
%  sunits/.is choice,%
%  sunits/.initial=alternate,%
%  sunits/.default=derived,%
%  sunits/base/.code={\perpusebaseunits},%
%  sunits/derived/.code={\perpusederivedunits},%
%  sunits/alternate/.code={\perpusealternateunits},%
%}%

\NewDocumentCommand{\settheunits}{ o }{%
  \IfValueTF {#1}
    { \pgfkeys{/selectunits,#1} }
    { \csname perpuse\pgfkeysvalueof{/selectunits/sunits}units\endcsname }
}%

\settheunits\par
\settheunits[sunits]\par
\settheunits[sunits=base]\par
\settheunits[sunits=derived]\par
\settheunits[sunits=alternate]\par
\settheunits[sunits]\par
\settheunits\par
\settheunits[sunits=blubb]\par % This will halt processing with an error.

\end{document}
8
  • 1
    In your current solution using .code{\csname perpuse#1units\endcsname} instead of a .choice type, you don't check whether the value is a valid choice. Instead, if I now input sunits=blubb it'll be silently ignored (and the control sequence \perpuseblubbunits will have the meaning \relax).
    – Skillmon
    Jan 21, 2021 at 10:02
  • 1
    Yes I'm aware there's no error checking or validation. Jan 21, 2021 at 11:19
  • 1
    @Skillmon I added a sanity check for the key value. Jan 21, 2021 at 19:30
  • 1
    Better would be \ifcsname perpuse#1units\endcsname\csname perpuse#1units\endcsname\else\GenericError....\fi.
    – Skillmon
    Jan 21, 2021 at 20:14
  • 1
    For some reason I had thought \ifcsname wouldn't work, but it does. Edited to reflect. Jan 21, 2021 at 21:27

You must log in to answer this question.

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