I have a need for a struct analogue in Mathematica. After reading this post I came to the conclusion that System`Utilities`HashTable is one of the best options. But it's rather terse to write. Let's say we have a hash table:

4 Answers
4

If you're willing to use a slightly different syntax to invoke System`Utilities`HashTableAdd, you can create your own wrapper around System`Utilities`HashTable that does most of what you want without modifying any built-in functions. The loss of the convenient hashTable.key = value syntax is unfortunately necessary because you can't use TagSet to set a tag more than one level deep in an expression, but personally I think that the postfix form hashTable.key.set[value] isn't much worse. Edit: by introducing a wrapper symbol specifically for this functionality it is also possible to overload Dot reasonably safely and enable the use of infix form as per the question (see below).

As a demonstration, let's say we find ourselves forgetting what 1 + 1 is and want to store it in a hash table. And perhaps we want to store the inverse of this operation as well. (These contrived examples help to demonstrate usage with unevaluated expressions for both the key and the value.)

As this example shows, the key is evaluated unless otherwise specified using Unevaluated. (The parentheses in hash.(2).set[...] are necessary as otherwise this will be interpreted as a multiplication: 0.2 hash set[...].) Now we can write:

hash.Unevaluated[1 + 1].get[]

or simply (since get[] is an optional argument)

hash.Unevaluated[1 + 1]

and get back 2. Similarly, if we write hash.(2).get[] or hash.(2), "the value was evaluated" is printed and the unevaluated value 1 + 1 is evaluated to return 2.

Maybe we think that the second definition isn't very useful since it'll be evaluated every time. So, we redefine it:

hash.(2).set@Unevaluated[1 + 1];

which clears the previous definition automatically. (Printing "the value was evaluated" in the process. This is the result of System`Utilities`HashTableRemove's own behaviour which is to return the value associated with the key that was removed, if any.) Now hash.(2) will give, more usefully, Unevaluated[1 + 1].

Finally we come to our senses and decide to get rid of these trivial hash table entries. Again, because this calls System`Utilities`HashTableRemove, the values of the keys to be cleared are returned:

hash.Unevaluated[1 + 1].clear[]

2

and

hash.(2).clear[]

Unevaluated[1 + 1]

System`Utilities`HashTableRemove throws an error if invoked on a nonexistent key, but clearing such in this way simply does nothing, returning Null, as I thought this behaviour was more useful. However, an attempt to read undefined keys still fails:

hash.undefined

$Failed

Incidentally, you can have keys containing Dot without any problems, thanks to Dot's attributes Flat and OneIdentity. This one contains Dot in two different ways:

Edit: implementing infix syntax for Set

Leonid makes a point very well here regarding overloading Set, which actually applies to any built-in function. When overloading any built-in, one has to ask oneself:

If two people did this at the same time, without being aware of each other, could it result in a conflict?

Obviously, the only acceptable overloads are those for which we can answer, "No". In practice this usually means creating an "environment" using Internal`InheritedBlock within which different semantics apply, introducing one's own symbols that are not likely to conflict with anyone else's (or even, using Module, ones that are unable in principle to cause a conflict), and writing definitions carefully so that the scope is tightly constrained and no evaluation leaks are introduced. In this spirit, here is my suggestion for how to implement, by means of the code above, the hashTable.key = value syntax (and SetDelayed and Unset at the same time, why not):

Be aware, however, that although the semantics are close to those of Set, SetDelayed, and Unset, they aren't exactly the same because of the implementation in terms of the hash table functions themselves. Nor are they exactly the same as for the postfix form: for example, while one can write e.g. hash.(2).set@Unevaluated[1 + 1] to set an unevaluated value, the equivalent in the infix form requires two applications of Unevaluated, i.e. hash.(2) = Unevaluated@Unevaluated[1 + 1] (which is consistent with Set itself).

If, in a session, one wishes for these semantics to apply over a number of evaluations, a good way to propagate the environment is to set $Pre = withHashTableSetSemantics. This way, no global settings are modified, and the environment can be disabled again if it causes problems or when no longer needed by Unseting $Pre.

Set has attribute HoldFirst, which on one hand allows to use it on h.value despite the definition for h.value, but on the other hand inhibits evaluation of h, so it won't match _System`Utilities`HashTable if it expands to such a type. Here the /; comes into play: This causes an evaluation leak which normally is not wanted, but in this case allows us to check the resulting pattern for matching. Therefore the rule only applies if the left hand side really evaluates to a System`Utilities`HashTable.

Mathematica is a registered trademark of Wolfram Research, Inc. While the mark is used herein with the limited permission of Wolfram Research, Stack Exchange and this site disclaim all affiliation therewith.