TL;DR
The wrapper Key
is interpreted verbatim by all ?Key*
and Association
-related functions, the current exception being Lookup
which strips one level of Key
.
Other symbols prior to the introduction of associations, that act on expressions of any head and have a position-related argument, ask for the wrapper Key
in order to be used in a special way on associations, except for keys specified as strings.
About consistency
Are there any consistent rules about using Key
?
Proposition. When the syntax of a function explicitly asks for a key as an argument, wrapping this key key
in the symbol Key
defines a different key, the expression of which is Key[key]
.
Leaving aside the symbols KeyDrop
and Lookup
which I address further down, this rule seems to apply on all ?Key*
symbols and other Association
-related functions, in the mentioned versions 10.4.1, 11.0.0 and 11.0.1.
As examples about this rule and its constancy, consider KeyExistsQ
and KeyMemberQ
. We have in all three versions:
KeyExistsQ[<|a -> 1|>, a]
(* True *)
KeyExistsQ[<|a -> 1|>, Key[a]]
(* False *)
KeyMemberQ[<|a -> 1|>, a]
(* True *)
KeyMemberQ[<|a -> 1|>, Key[a]]
(* False *)
which shows that the keys key
and Key[key]
are different. Note that the last input used to give True
in 10.2 and earlier, so the change made in 10.3 seems to support the proposed rule.
About the change of behavior in KeyDrop
What is a possible motivation behind that change? [from OP's comment]
In versions 10.4.1 and earlier, KeyDrop
fails to make the distinction between the arguments key
and Key[key]
. One level of Key
was stripped:
$Version
(* "10.4.1 for Microsoft Windows (64-bit) (April 11, 2016)" *)
KeyDrop[<|a -> 1|>, a]
(* <||> *)
KeyDrop[<|a -> 1|>, Key[a]]
(* <||> *)
KeyDrop[<|Key[a] -> 1|>, Key[a]]
(* <|Key[a] -> 1|> *)
KeyDrop[<|Key[a] -> 1|>, Key[Key[a]]]
(* <||> *)
This wrong behavior is fixed as of 11.0.1 and solves the inconsistency with all other ?Key*
symbols.
Special cases
About Lookup
Lookup
explicitly asks for a key,
Lookup[assoc, key]
looks up the value associated with key
in the association assoc
so it should satisfied the proposed rule, but this is not the case:
Lookup[<|a -> 1, b -> 2|>, a]
(* 1 *)
Lookup[<|a -> 1, b -> 2|>, Key[a]]
(* 1 *)
Lookup
could be seen as an exception to the rule, and its particular behavior is documented in the sections "Properties & Relations" and "Possible Issues".
About other symbols
There are symbols that need the keys to be wrapped into Key
in order to be correctly interpreted. These symbols all have definitions prior to the introduction of associations, which probably explain why a particular syntax is needed to get an unambiguous additional definition.
For instance, prior to 10.0, Part
accepts only integers or valid Span
specifications. Using the wrapper Key
allow to distinguish between the position in the association and the corresponding key, stripping one level of Key
in the process:
assoc = <|a -> x, 1 -> y, Key[1] -> z|>;
assoc[[1]]
(* x *)
assoc[[Key[1]]]
(* y *)
assoc[[Key[Key[1]]]]
(* z *)
Similarly, we have for other symbols with a position-related argument:
Extract[assoc, 1]
(* x *)
Extract[assoc, Key[1]]
(* y *)
ReplacePart[assoc, 1 -> 0]
(* <|a -> 0, 1 -> y, Key[1] -> z|> *)
ReplacePart[assoc, Key[1] -> 0]
(* <|a -> x, 1 -> 0, Key[1] -> z|> *)
MapAt[f, assoc, 1]
(* <|a -> f[x], 1 -> y, Key[1] -> z|> *)
MapAt[f, assoc, Key[1]]
(* <|a -> x, 1 -> f[y], Key[1] -> z|> *)
For all these symbols, as mentioned by Kuba in a comment below, a key specified as a string need not to be wrapped in Key
. This shortcut is unambiguously defined, and therefore distinguishable from other available syntaxes. Taking the example of Part
:
<|"a" -> 1|>[[Key["a"]]]
(* 1 *)
<| "a" -> 1 |>[["a"]]
(* 1 *)
KeyDrop
andKeyTake
is taken literally as a single key or a list of keys.KeyDrop[assoc, Key[a]]
will not work unless there is aKey[a]
as an actual key. (2) You could doKeyTake[<|a -> 1, b -> 2, {a, b} -> 3|>, {{a, b}}]
. $\endgroup$Lookup
andPart
are honoringKey
s. Nevertheless such change should be emphasized in documentation. $\endgroup$