# Use choice key with choices setting booleans

When defining choice keys with l3keys2e there are two possible ways and I use the one with sub-keys in a new package. When defining watch each value should do interface3.pdf says that I can use .code which is done for key-b in the following example but I’d like to set the booleans directly with .bool_set as can be seen in key-a. Unfortunatly this doesn’t seem to work: When using key-a=ch-a as option the compilation hangs.

\begin{filecontents}{test.sty}
% load xparse and identify package
\RequirePackage{xparse}
\ProvidesExplPackage{test}{2014/05/20}{0}{}

\RequirePackage{l3keys2e}

% define bools for key-b
\bool_new:N \l_test_key_b_a_bool
\bool_new:N \l_test_key_b_b_bool
% define keys
\keys_define:nn { testkeys } {
% this won't work
key-a .choice:,
key-a / ch-a .bool_set:N = \l_test_key_a_a_bool,
key-a / ch-b .bool_set:N = \l_test_key_a_b_bool,
% this will work
key-b .choice:,
key-b / ch-a .code:n = { \bool_set_true:N \l_test_key_b_a_bool },
key-b / ch-b .code:n = { \bool_set_true:N \l_test_key_b_b_bool },
}
\ProcessKeysOptions{ testkeys }

% demo-commands (real life is more complex ...)
\NewDocumentCommand { \getChoiceA } { } {
\bool_if:NT \l_test_key_a_a_bool { ch-a }
\bool_if:NT \l_test_key_a_b_bool { ch-b }
}
\NewDocumentCommand { \getChoiceB } { } {
\bool_if:NT \l_test_key_b_a_bool { ch-a }
\bool_if:NT \l_test_key_b_b_bool { ch-b }
}
\end{filecontents}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\documentclass{article}

\usepackage[
%   key-a=ch-a,% uncomment this line to see the problem
key-b=ch-a
]{test}

\begin{document}
Choice of key-a was: \getChoiceA

Choice of key-b was: \getChoiceB
\end{document}


Is it a bug that .bool_set can’t be used her or are there some smart considerations I don’t see?

• Could you explain how you expect the first approach to work? I don't see where the user is supposed to supply the true/false input. – Joseph Wright May 21 '14 at 20:27
• @JosephWright: As Jura said in his (her?) answer I had a little fallacy. I wanted key-a/ch-a to set the boolean true without a possibility to reset the bool / set it to false. SO maybe the current implementation is fine and kept me from doing some illogical. I need to think again how to implement another solution to reset the option. – Tobi May 21 '14 at 20:57

It does look like a bug to me, or at least highly non-ideal behaviour.

Keys defined with the .bool_set:N property do technically expect to be given a value (either true or false). Notice that the property of the key key-a / ch-a isn't .bool_set_true:N. In fact, such a property isn't implemented in l3keys at present. So an argument could be made that the way you define key-a goes against the intended syntax, while the way you define key-b is correct.

However, the crucial difference between a nested case like yours and the non-nested case like

\keys_define:nn { testkeys } { key-c .bool_set:N = \l_test_key_c_bool }
\keys_set:nn { testkeys } { key-c }


is that in the non-nested case, the boolean will be set to true by default if no value is provided. The same happens if you pass key-a / ch-a instead of key-a = ch-a to your package.

Adding an explicit default for ch-a to your code with

\keys_define:nn { testkeys }
{
key-a / ch-a .default:n = { true } ,
key-a / ch-b .default:n = { true }
}


doesn't help either. Passing key-a = { ch-a = true } produces an error because in that case l3keys looks for a choice under key-a that's called "ch-a=true" instead of passing true to ch-a.

Finally, the fact that the compilation of your code hangs instead of producing an error is a good sign that something's gone wrong inside l3keys. I don't know the details of the implementation off the top of my head, and I don't have time today to dig into the source to see what's amiss—but there are actual experts here who can do a much better job of that anyway! In the meantime, it looks like what you've done with key-b is the way to go.

## Update (21 May 2014, 19:40 GMT)

A perusal of source3 reveals the following underlying cause of the problem. In order to ensure that the a key defined with the .bool_set:N property only accepts true and false as values, it is internally defined as a choice key. In effect, writing

\keys_define:nn { testkeys } { key-c .bool_set:N = \l_test_key_c_bool }


is equivalent to writing

\keys_define:nn { testkeys }
{
key-c           .choice:                                              ,
key-c           .default:n = { true }                                 ,
key-c / true    .code:n    = { \bool_set_true:N  \l_test_key_c_bool } ,
key-c / false   .code:n    = { \bool_set_false:N \l_test_key_c_bool } ,
key-c / unknown .code:n    =
\__msg_kernel_error:nnx { kernel } { boolean-values-only }
{ \l_keys_key_tl } ,
}


Since nesting choice keys (quite reasonably) doesn't work, defining one of the choices of a another choice key with .bool_set:N doesn't work. Indeed, because of the way the paths of choice keys are set, doing what you did with your key-a leads to an endless loop, just like the following

\keys_define:nn { testkeys }
{
foo                 .choice:                ,
foo                 .default:n = { bar }    ,
foo / bar           .choice:                ,
foo / bar           .default:n = { bar-a }  ,
foo / bar / bar-a   .code:n    = { BAR-A }  ,
foo / bar / bar-b   .code:n    = { BAR-B }  ,
foo / buzz          .choice:                ,
foo / buzz          .default:n = { buzz-a } ,
foo / buzz / buzz-a .code:n    = { BUZZ-A } ,
foo / buzz / buzz-b .code:n    = { BUZZ-B } ,
}
\keys_set:nn { testkeys } { foo = bar }


While I think this behaviour is fine for self-evident choice keys, I don't think it's right for .bool_set:N, so I'll send a fix request to the latex3 mailing list.

• Thanks for your answer so far. I’ll accept it since the “wrong syntax” argument is convincing. But a more intuitive way in future versions of L3 would be nice :-) – Tobi May 21 '14 at 17:06
• Okay, I've now looked at source3 and figured out what goes wrong. I've edited the answer to make it a more complete. – Jura Pintar May 21 '14 at 18:57