Fractional Keys in Associations

I got into an unfortunate situation, involving fractions that can get changed by evaluation, which occur as (part of) keys in my associations. I will probably steer far away from the entire situation, mostly because of the following quote.

In this answer, with respect to evaluation of keys in associations, Taliesin tells us

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.

Strange Example

Anyway, I would still like to know what exactly could be going on in the following example. The evaluations in this question were executed using version 10.3.

assoc = Association[];
assoc[1/2] = "rational?";
assoc[Rational[1, 2]] = "rational exposed";
assoc[Unevaluated@Rational[1, 2]] = "rational unevaluated";
(*FF is short for FullForm*)
assoc[Unevaluated[1/2]] = "big FF";


Note that in the following evaluation, the first two expressions have the same FullForm, so of course they should evaluate in the same way. Perhaps nothing seems strange yet

assoc[[Unevaluated@Key[1/2]]]
assoc[[Unevaluated@Key[Times[1, Power[2, -1]]]]]
assoc[[Unevaluated@Key[Rational[1, 2]]]]
assoc[1/2]

"big FF"
"big FF"
"rational unevaluated"
"rational exposed"


However, it seems there are two expressions with the same FullForm in the same association. Here are the InputForm and FullForm of the key-value pairs.

List @@@ Hold @@ KeyValueMap[Hold, assoc2] // InputForm

Hold[{1/2, "rational exposed"}, {Rational[1, 2], "rational unevaluated"},
{1/2, "big FF"}]

List @@@ Hold @@ KeyValueMap[Hold, assoc2] // FullForm

Hold[List[Rational[1,2],"rational exposed"], List[Rational[1,2],
"rational unevaluated"], List[Times[1,Power[2,-1]],"big FF"]]


Note that 1/2 evaluates to Rational[1,2]. It can be shown with another test that the definition corresponding to "rational exposed" has overwritten the one corresponding to "rational?".

My best guess for explaining this: Possibly the internal representation of integers is different when we enter the expressions in these different ways.

Question: What can explain this example?

• Fortunately, the analogue for functions with DownValues works correctly. Commented Nov 4, 2015 at 14:16
• FullForm /@ (1/2 // Trace)? Commented Nov 4, 2015 at 14:24
• obligatory question: what version of mathematica are you using? Commented Nov 4, 2015 at 14:41
• @rcollyer I have included that information (10.3) in the question. Maybe things will change in the future, but maybe not, as Szabolcs shows that the behavior may not be so unreasonable. Commented Nov 4, 2015 at 14:48
• @JacobAkkerboom I would agree with that assessment. Commented Nov 4, 2015 at 15:03

There are three keys:

<|1/2 -> "rational exposed",
Rational[1, 2] -> "rational unevaluated",
1/2 -> "big FF"|>


Two of these look the same. The difference between "rational exposed" and "big FF" is made clear by FullForm, as you showed.

Then the question boils down to: What is the difference between "rational exposed" and "rational unevaluated"? They look the same in FullForm!

"rational exposed" is an atomic expression of type Rational. Even though its FullForm shows Rational[1,2], which looks compound, it is not.

"rational unevaluated" is a compound expression with three parts: Rational, 1 and 2. When given the chance, this will immediately evaluate to an atomic rational.

I discussed why atomic expressions have compound representation in this thread. In short: I believe it's to allow serialization. This way any expression can be passed through a MathLink connection (without needing to update MathLink every time a new kind of atomic type is introduces). Similarly, every expression will have a textual representation (input form) that looks like any other Mathematica expression.

In general, I believe it is best to avoid keeping these equivalent but distinct alternative forms unevaluated. To avoid problems, they should be allowed to evaluate to their final form (the atomic version). Otherwise just passing them through any alternative representation (e.g. exporting to a non-MX format or passing through MathLink, even internally!) will trigger conversions between forms and will induce weird problems.

In principle, the unevaluated form we get could even depend on how the expression is input. Things entered through the front end pass through a box representation, while things entered in terminal mode don't. There's no reason why in principle 1/2 could be directly parsed to either of the three forms you showed, without going through an additional evaluation step.

• Related: mathematica.stackexchange.com/a/97332/12 This shows how to convert an atomic expression to its equivalent compound form (without resorting to FullForm). Commented Nov 4, 2015 at 14:28