6

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
6

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.