# Held keys in associations

Bug introduced in 10.0 and persisting through 11.0.1 or later
Bug introduced in 10.0 and fixed in 10.4

Association is new in 10.0.

Bug still present: AssociationMap[Hold, p]
Fixed bug: Hold /@ p

Jacob Akkerboom commented:

Note that PositionIndex does work correctly with held expressions, whereas this is a bit painful to implement using GatherBy. a = 1; PositionIndex[Unevaluated[{a, b, c, d}]]

I finally got around to exploring this behavior and I am a bit confused and troubled by what I see.

First Jacob's example with a more explicit definition of a:

a = "Fail!";
p = PositionIndex[Unevaluated[{a, b, c, d}]]

<|a -> {1}, b -> {2}, c -> {3}, d -> {4}|>


We see from this that Association is capable of containing unevaluated key names. We can also extract the value corresponding to the key, or use the Association in a replacement:

p[Unevaluated @ a]

Hold[a, b, c] /. p

{1}

Hold[{1}, {2}, {3}]


Other operations prove problematic however. In the first and third cases a evaluates undesirably:

Hold /@ p
KeyMap[Hold, p]
AssociationMap[Hold, p]

<|"Fail!" -> Hold[{1}], b -> Hold[{2}], c -> Hold[{3}], d -> Hold[{4}]|>

<|Hold[a] -> {1}, Hold[b] -> {2}, Hold[c] -> {3}, Hold[d] -> {4}|>

Association[{Hold["Fail!" -> {1}], Hold[b -> {2}], Hold[c -> {3}], Hold[d -> {4}]}]


Since KeyMap does work I thought we could use HoldPattern for the keys, but that too has problems in that the original lookups and replacements no longer work:

p2 = KeyMap[HoldPattern, p]

<|HoldPattern[a] -> {1}, HoldPattern[b] -> {2}, HoldPattern[c] -> {3}, HoldPattern[d] -> {4}|>

p2[Unevaluated @ a]
Lookup[p2, Unevaluated[a]]
Hold[a, b, c] /. p2

Missing["KeyAbsent", "Fail!"]

Missing["KeyAbsent", Unevaluated[a]]

Hold[a, b, c]


The lookup does work if we use an explicit HoldPattern but that seems somewhat undesirable:

p2[HoldPattern @ a]

{1}


To make the replacement work we can convert the Association to a Dispatch expression:

Hold[a, b, c] /. Dispatch[p2]

Hold[{1}, {2}, {3}]


My questions:

1. Is there a better way to work with unevaluated keys in an Association?

2. Is the illustrated behavior intended and desirable?

3. Are unevaluated keys an expected application of Association? The behavior of PositionIndex indicates it may be but the conflicting results of the mapping operators seem unplanned.

• Based on the second point in Tali's authoritative answer I have added but bugs tag. – Mr.Wizard Jul 29 '15 at 23:43
• I think my answer here sheds some light on the situation after version 10.4.0. I believe the advice by WRI is "do not use unevaluated keys". The fact that you can create an Association with unevaluated keys using PositionIndex then puts us in a strange situation. I think maybe this should be considered a bug of PositionIndex. Maybe it does not make sense to expect anything from AssociationMap in this case, as this case is so unusual, so that maybe this should not be marked as a bug in AssociationMap. – Jacob Akkerboom Nov 29 '16 at 9:53

1. Wrap the entire key in Hold, if you really want to keep things that might evaluate in the keys of an association (HoldPattern is a red herring: keys aren't patterns). Alternatively use ToString. But generally this just sounds like a dangerous and confusing game to play, to me. What exactly are you trying to do? There is probably a better way that doesn't involve using the keys of associations.

2. No, Map and AssociationMap shouldn't evaluate keys. Please report that behavior to tech support and it'll wend its way to Konstantin. If it's a simple fix it'll probably happen for 10.0.2, if it requires a big rewrite or a loss in efficiency it may take longer.

3. Keys have to remain unevaluated for associations to have any efficiency advantages over ordinary lists of rules. There's no way around that.

• Thanks, yet again, for replying. Regarding: "What exactly are you trying to do?" my answer is: only trying to understand how this is supposed to work. The fact that PositionIndex returned an association with unevaluated keys seemed at least unusual and worth exploring. – Mr.Wizard Sep 24 '14 at 6:10
• Would you please tell me if also values are to remain unevaluated? I quoted you in answering (89483) then realized I may be misrepresenting the facts. – Mr.Wizard Jul 30 '15 at 17:31
• To add to this answer, when you Save or Compress an Association and load it, it triggers evaluations of its keys. Theoretically this can be overcome with InternalInheritedBlock, but that is not safe and there are even more problems – Jacob Akkerboom Nov 4 '15 at 15:24
• @JacobAkkerboom that's worth reporting. – Taliesin Beynon Nov 24 '15 at 19:06
• @TaliesinBeynon In that case I will put it on my to do list, but I will probably report some other issues first, that can be communicated more easily. One reason is that I did not yet get a reply to my mail about this issue and I was not really happy about my presentation of the problems. I asked support to put you in the CC, as you requested. It also seems... – Jacob Akkerboom Nov 24 '15 at 19:26

In 10.1.0 we have the new function KeyValueMap. You can use these functions, although they are not perfect

heldNormal[assoc_] :=
Apply[Rule, Hold@Evaluate@KeyValueMap[Hold, assoc], {2}]
map[f_, assoc_] :=
Association @@
Unevaluated @@@ List@MapAt[f, heldNormal@assoc, {All, All, 2}]
associationMap[f_, assoc_] :=
Association[ReleaseHold@Map[f, heldNormal[assoc], {2}]]


Example

With

testAssoc = Association[Unevaluated[{a -> b, c -> b}]];
b := (Print@#; #) &@"Fail";
a := (Print@#; #) &@"Boo";


We get

map[Hold, testAssoc]
associationMap[
Function[Null,
Identity @@ RuleDelayed @@@ MapAt[Hold, Hold[#], {{1, 1}}],
HoldAll], testAssoc]

<|a->Hold[b],c->Hold[b]|>
<|Hold[a]:>b,Hold[c]:>b|>


Notes

Unfortunately heldNormal does not know whether to use Rule or RuleDelayed, which Normal does do. For this purpose, we could consider an alternative using something like the following snippet

Normal[Hold /@ KeyMap[Hold, testAssoc]]


Perhaps this answer can be extended with alternatives to other members of the *By` family. See my answer here.

• I don't know why I did not see this until today. A belated thanks and +1. – Mr.Wizard Jun 23 '15 at 12:43
• @Mr.Wizard you're welcome :) – Jacob Akkerboom Jun 23 '15 at 15:27