# How to distinguish "no value" from "empty value" when setting l3keys?

I'm trying to create an option with l3keys such that, if it receives a value, even if an empty one, it gets added to a property list, but if it receives no value, it gets removed from the property list.

However, I don't seem to be able do distinguish key from key= in a .code:n "action". I know that .default:n captures that situation, but it cannot receive a code block, just a value. I also know that I can distinguish the situation in \keyval_parse:nnn, but I'd like this behavior in some options among the whole set, and using \keyval_parse:nnn for the whole thing would become complicated.

The MWE illustrates the situation:

\documentclass{article}

\ExplSyntaxOn
\prop_new:N \l__myprops_prop
\keys_define:nn { options }
{
mykey .code:n =
{
\tl_if_novalue:nTF {#1}
{ \prop_remove:Nn \l__myprops_prop { mykey } }
{ \prop_put:Nnn \l__myprops_prop { mykey } {#1} }
\prop_show:N \l__myprops_prop
} ,
mykey2 .code:n =
{
\tl_if_empty:nTF {#1}
{ \prop_remove:Nn \l__myprops_prop { mykey2 } }
{ \prop_put:Nnn \l__myprops_prop { mykey2 } {#1} }
\prop_show:N \l__myprops_prop
}
}
\ExplSyntaxOff

\begin{document}

\ExplSyntaxOn
\keys_set:nn { options } { mykey }
\keys_set:nn { options } { mykey= }
\keys_set:nn { options } { mykey=val }
\prop_clear:N \l__myprops_prop
\keys_set:nn { options } { mykey2 }
\keys_set:nn { options } { mykey2= }
\keys_set:nn { options } { mykey2=val }
\ExplSyntaxOff

\end{document}


The log for this document contains:

The property list \l__myprops_prop contains the pairs (without outer braces):
>  {mykey}  =>  {}.

l.27 \keys_set:nn { options } { mykey }

The property list \l__myprops_prop contains the pairs (without outer braces):
>  {mykey}  =>  {}.

l.28 \keys_set:nn { options } { mykey= }

The property list \l__myprops_prop contains the pairs (without outer braces):
>  {mykey}  =>  {val}.

l.29 \keys_set:nn { options } { mykey=val }

The property list \l__myprops_prop is empty
> .

l.31 \keys_set:nn { options } { mykey2 }

The property list \l__myprops_prop is empty
> .

l.32 \keys_set:nn { options } { mykey2= }

The property list \l__myprops_prop contains the pairs (without outer braces):
>  {mykey2}  =>  {val}.

l.33 \keys_set:nn { options } { mykey2=val }


Which shows that neither key nor key= pass the "no value" test, and both key and key= pass the emptiness test. Unfortunately, I cannot use emptiness to "signal" the removal, since "empty" is a valid value for the key in question. Is indeed \keyval_parse:nnn the only choice here?

• Unfortunately, it is not possible with \keys_set:nn (using documented interfaces). There is a private boolean variable that holds the value/no-value state, but it's not used when the .code:n handler is used. It would be nice to distinguish these cases, but I don't think a change is feasible at this point... Sep 9, 2021 at 0:02
• Hi @PhelypeOleinik, I feared as much... :-( I'll have to rethink this here then. From the OPs perspective, your comment is a full answer. If you'd like to make it one, I'd be glad to accept it. Either way, thank you! Sep 9, 2021 at 0:20
• Conceptually, l3keys works on the idea that keys are properties, and thus there is no such thing as 'not having a value' for any key - once you create a key, it always has some value even if that value is 'empty'. Sep 9, 2021 at 17:14
• @JosephWright Thanks for the comment. What I'm trying to do is to get different behaviors for key and key= in a .code:n handler. So I might have expressed myself poorly, but since \keyval_parse:nnn recognizes this difference, I was hoping to grab that piece of information in \keys_set:nn too. And indeed, Skillmon provided a way which seems to be the proper one to do it. Sep 9, 2021 at 17:23
• @JosephWright Let me see if I get what you meant correctly. Are you saying that, even though I can technically recognize the difference between key and key=, I should not use that distinction at the user level? Sep 9, 2021 at 17:42

This builds on the same basic idea as @Phelype's answer, namely use \c_novalue_tl as a marker, but instead of wrapping \keyval_parse:nnn around \keys_set:nn this uses the .default:x handler to set the default value to \c_novalue_tl:

\documentclass{article}

\ExplSyntaxOn
\prop_new:N \l__myprops_prop
\cs_new_protected:Npn \__myprops_handle:n #1
{
\tl_if_novalue:nTF {#1}
{ \prop_remove:NV \l__myprops_prop \l_keys_key_str }
{ \prop_put:NVn   \l__myprops_prop \l_keys_key_str {#1} }
\prop_show:N \l__myprops_prop
}
\keys_define:nn { options }
{
mykey  .code:n = \__myprops_handle:n {#1}
,mykey  .default:x = \c_novalue_tl
,mykey2 .code:n = \__myprops_handle:n {#1}
,mykey2 .default:x = \c_novalue_tl
}
\ExplSyntaxOff

\begin{document}

\ExplSyntaxOn
\keys_set:nn { options } { mykey }
\keys_set:nn { options } { mykey= }
\keys_set:nn { options } { mykey=val }
\prop_clear:N \l__myprops_prop
\keys_set:nn { options } { mykey2 }
\keys_set:nn { options } { mykey2= }
\keys_set:nn { options } { mykey2=val }
\ExplSyntaxOff

\end{document}

• Oh, wow! We can have both .code: and .default: and .code: is executed after .default:. That's clever! Thank you! How did you find that out? Sep 9, 2021 at 10:35
• @gusbrs knowing much of the implementation helps :P But more seriously, take a look at the documentation in texdoc interface3, the description of the .default:n property is: "Creates a <default> value for <key>, which is used if no value is given. This will be used if only the key name is given, but not if a blank <value> is given: [code example]" The code example covers exactly your use case (well, not the property setting, but using the .code:n property). The .default:n property does nothing by itself, it just sets a default value used with whichever other property is defined. Sep 9, 2021 at 10:51
• :-) Well, I had. Currently texdoc interface3 is deserving a dedicated keybinding here. And I was acquainted with the documentation of .default: in particular. I just didn't figure it out it could be used alongside .code:, much less of their execution order. The docs do say that some of the "actions"/handlers are mutually exclusive, but just gives some clear examples, not an exhaustive list. And I'd be willing to bet there's no word about .code: coming after .default:. I guess it takes "right eyes" to see it. :-) Thanks once again! Sep 9, 2021 at 11:18
• "The code example covers exactly your use case". In hindsight, indeed it does. Damn, it is hard to have a thick skull! ;-) Sep 9, 2021 at 11:33
• @gusbrs quick list of exclusive handlers: Every one except .default:n, .initial:n, .groups:n. Additionally .value_required:n and .value_forbidden:n are only mutually exclusive to each other and not to any other handler. (I hope I didn't forget something here...) Sep 9, 2021 at 11:41

There is no built-in way to distinguish mykey from mykey={} when it's declared with the .code:n handler. Internally these cases are different, and there is a boolean variable that keeps track of that, but all using private variables from l3keys.

What you can do using the available interfaces is use \keyval_parse:nnn, as you suggested, to differentiate both cases, and then when mykey is used, turn that into mykey = \c_novalue_tl, then the code in your key can detect that using \tl_if_novalue:nTF. This makes the parsing a bit slower than just using \keys_set:nn because some common bookkeeping code in \keys_set:nn is executed more often, but that's likely minimal.

The example below implements \gusbrs_keys_set:nn, which passes \c_novalue_tl to a key if no value is given:

\documentclass{article}

\ExplSyntaxOn
\cs_new_protected:Npn \gusbrs_keys_set:nn #1 #2
{
\keyval_parse:nnn
{ \__gusbrs_keys_set:nn {#1} }
{ \__gusbrs_keys_set:nnn {#1} }
{#2}
}
\cs_new_protected:Npn \__gusbrs_keys_set:nn #1 #2
{ \use:x { \keys_set:nn {#1} { #2 = \c_novalue_tl } } }
\cs_new_protected:Npn \__gusbrs_keys_set:nnn #1 #2 #3
{ \keys_set:nn {#1} { #2 = #3 } }
\ExplSyntaxOff

\ExplSyntaxOn
\prop_new:N \l__myprops_prop
\keys_define:nn { options }
{
mykey .code:n =
{
\tl_if_novalue:nTF {#1}
{ \prop_remove:Nn \l__myprops_prop { mykey } }
{ \prop_put:Nnn \l__myprops_prop { mykey } {#1} }
\prop_show:N \l__myprops_prop
} ,
}
\ExplSyntaxOff

\begin{document}

\ExplSyntaxOn
\gusbrs_keys_set:nn { options } { mykey }
\gusbrs_keys_set:nn { options } { mykey= }
\gusbrs_keys_set:nn { options } { mykey=val }
\ExplSyntaxOff

\end{document}


This is printed on the terminal:

The property list \l__myprops_prop is empty
> .

l.35 \gusbrs_keys_set:nn { options } { mykey }

?
The property list \l__myprops_prop contains the pairs (without outer braces):
>  {mykey}  =>  {}.

l.36 \gusbrs_keys_set:nn { options } { mykey= }

?
The property list \l__myprops_prop contains the pairs (without outer braces):
>  {mykey}  =>  {val}.

• But, one question, what happens if I have other keys besides mykey and some of them receive no value (they either have a .default:n or are .meta:n action with value forbidden), what will happen to them? Sep 9, 2021 at 1:14
• @gusbrs Hm... Is “won't work” an acceptable answer? ;-) Sorry, I didn't think of that. Maybe something with \keys_set_groups:nnn, and placing mykey (and other similar keys) in a separate group might work (guessing). It's getting late, so I'll give it a try tomorrow, if you haven't managed by then Sep 9, 2021 at 1:21