Welcome! Please see the About page for a little more info on how this works.

0 votes
in data.int-map by
edited by

Here's a question, in Clojure there are our typical HashMaps which allow assoc'ing any kind of keys on them, and then we have some more specialized kinds of maps for specific keys. Most of them are however also open to allow assoc'ing keys outside their usual range, gracefully reverting to hashmaps when the operation would break the data structure's invariants. Examples below:

Records are open to assoc, and revert to hashmaps when dissoc'ing a record field

(defrecord Person [name lang])
;; => clojure.data.int_map_test.Person
(let [p (->Person "john" "en_CA")]
  [p (assoc p :foo :bar) (dissoc p :lang)])
;; =>
;; [#clojure.data.int_map_test.Person{:name "john", :lang "en_CA"}
;; #clojure.data.int_map_test.Person{:name "john", :lang "en_CA", :foo :bar}
;; {:name "john"}]

On the other hand, struct-maps are open to assoc, but closed to dissoc

(let [sm (struct-map (create-struct :name :lang) :name "John" :lang "en_CA")]
  [(assoc sm :foo "bar")
   (try (dissoc sm :lang) (catch Exception e (.toString e)))])
    => [{:name "John", :lang "en_CA", :foo "bar"} 
        "java.lang.RuntimeException: Can't remove struct key"]

An other example is ArrayMaps which eventually turn into HashMaps as they grow. That's not exactly the same, there's no specific key outside the map's range, but similar in a more general sense of, when an operation either violates invariants or performance expectations, fall back to the general HashMap.

That makes me wonder, how do people feel about int-map/int-set which instead throw exception if you try and add a non-integer key?

(assoc (i/int-map 1 2) :a "b")
ClassCastException class clojure.lang.Keyword cannot be cast to class java.lang.Number

Should it be modified to fall back to a hashmap instead? It seems like that is more in line with Clojure philosophy of open data structures.

It could be implemented similar to PersistentStructMap, which holds an extra map in which it stores extra key/value pairs which are not part of the struct's definition:

final IPersistentMap ext;

The same thing could be done in PersistentIntMap

(deftype PersistentIntMap
  [^INode root
   ^long epoch
   ^IPersistentMap ext]
  ;; ...

What do you think?

Also, the same question but for int-sets

edited by
I suppose the hairiest part of this would be to do with the casting behaviour. For instance, you can insert an int or a BigInt or a short into an int-map and it is cast as a long. But that is a different behaviour to HashMaps which just store the key however it's passed in. That alone might be enough to sink this whole idea. Still worth thinking about though

1 Answer

+1 vote
selected by
Best answer

The stated constraint of int-map/set is that they have integer keys, and can gain performance advantages for this use case. If you want that, you should use int-map. If you don't, you should use a standard map.

So, I think it's beneficial that adding a non-int key to an int-map throws, and this should not change.