I recently asked about composite keys in maps in clojure : How can you implement Composite keys in clojure? ...
The answer was that they work similar to java keys - if something overrides "equals", then it can be used effectively as a key.
I was wondering : Are there macros that allow us to override "equals" for custom data structures ? For example, say I wanted to use a Map as a key, and define uniqueness as "if this map contains 2 or more elements in common with another map, they are equal". How could I override the default behavior of a map ?
In java, I find this facility to be quite powerful when making high speed maps with thousands of beans as keys.
To hopefully add a bit of clarity to this discussion, in Java it is common to override hashCode
and equals
. For example, you are trying to keep track of employees that have names, but may also have a nickname. The goal is to make sure those employees are not duplicated. In that case, I would override equals
and hashCode
to only look at the employee's ID. When those employees are entered into a Set data structure, they are not duplicated. In Clojure, you could do :
(deftype Employee [name id]
Object
(equals [a b] (= (.id a) (.id b)))
(hashCode [this] (.hashCode (.id this)))
(toString [this] (.name this)))
(def vince (Employee. "Vince" 42))
(def vincent (Employee. "Vincent" 42))
(def tony (Employee. "Tony" 2))
(into #{} [vince vincent tony])
But you may wish to pursue a "pure Clojure" data structure solution rather than going the hashcode, equals, Java interop road.
It's not that clojure collections override equals; overriding equals happens almost everywhere you're doing something interesting. Clojure types provide an implementation of equals that is intended to be consistent for the whole language (arguably in a way that Java's equals was intended to be used). That means that things that are "equal" should be singular items in sets, and singular keys in maps, always, everywhere. The language design depends on that. Clojure 1.3 made some explicit non-backward compatible changes to get closer to that ideal.
Going against that expected behavior of equals is very likely going to cause trouble somewhere, somehow. And it's not too difficult to use your own set-like composites when you really need them without forcing your will on core equals anyway.
That said, you can use many java interop functions and macros to pervert the equals system if you really want to. See http://clojure.org/datatypes as a starting point.
This is madness in both Clojure and Java. Do Not Do This. You would be breaking the contract of the Object class, and the Map interface; all kinds of things would completely fall apart if they used your custom map class. If you state that two objects are equal
then their hashcodes must be identical, or else the HashMap holding them will be unable to cope. You could make everything hash to 0, of course, but there goes your high-performance map - it's now a linked list.
That said, you can do something like this in Clojure or Java by using a sorted-map
(or SortedMap
respectively) with a custom Comparator. However, there are zillions of pitfalls if you choose a Comparator that doesn't actually implement equality but instead some kind of "fuzzy equality".
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With