I often find myself in a situation where I really don't care whether I have a vector or a map:
[:foo :bar :baz :qux]
{0 :foo, 1 :bar, 2 :baz, 3 :qux}
The important functions (get
, assoc
, etc.) work on both. Some, like dissoc
, don't work on vectors, but they have good reasons not to.
However, I simply don't understand why keys
and vals
work on maps and not on vectors. Is there any good reason why they aren't implemented something like this (or maybe with a more elegant, polymorphic solution instead)?
(defn keys [m]
(if (vector? m)
(seq (range (count m)))
(clojure.lang.RT/keys m)))
(defn vals [m]
(if (vector? m)
(seq m)
(clojure.lang.RT/vals m)))
If there is no good reason, how can I go about trying to get this implemented in standard Clojure?
I think it is a valid idea that has merit.
The question of what functions should work with and what should be an error is difficult to draw a line on. At one end a language can be overly permissive (JavaScript?) to the point it allows you to hurt yourself. At the other end it can be so prescriptive that you are left without powerful abstractions that compose (C?).
In this particular case we are trading off the ability to detect an error where you are writing code that expects a map, but is passed a vector; against the benefit of being able to write more generic code. Personally I like that it is treated as an error because I sometimes do pass the wrong type of collection to a function, and prefer it to blow up than to do something.
I would find it confusing to call keys
on a vector because a vector doesn't have keys, it is indexable. To me they are different things. So if I write code that calls keys
on a vector, it is probably an error. If I really want a range that is the same size as the count of the vector, I would explicitly write that. And if I wanted to have a function that could handle both maps and vectors, I would use a type conditional to choose keys or range count. Obviously these are just my preferences, but for me they are a significant reason to not want keys
to work on vectors. Specifically the reason is that I want to detect mistakes.
However it is totally understandable that you would prefer it to work with vectors, seeing they are associative by index.
As for should dissoc
work as well, someone else might claim why not? (dissoc [1 2 3] 0) -> [2 3])
There is a performance issue because you cannot remove elements in O(1), well not really if Clojure adopted rbb-vector. It is very convenient sometimes when you have to do that operation. It is something people need to do, and is quite ugly and opaque in Clojure! Neither of us want this to be a feature, but I bet it would be really elegant in some situations. But it isn't really a case of a technical limitation, we just prefer it that way.
Clojure has an open contribution process which boils down to: Discuss what you’re trying to do with others on the Clojure Dev Google group. They’re likely to be able to offer comments and suggestions that will result in a higher-quality change and a smoother submission process. Once you’ve submitted the CA, you can submit patches via JIRA.
Anyone can submit a bug or enhancement request to Clojure. Anyone that has signed the contributor agreement can supply patches or work on improving tickets. Screeners have been granted the ability to move tickets through (some of) the stages of the process. BDFL - Rich Hickey is the creator and Benevolent Dictator for Life of what goes into Clojure. Stuart Halloway also has a special level of access and typically commits patches to Clojure.
If you feel this would be a good change to Clojure, I recommend discussing it in the Clojure Group first to garner some support for the idea and then bring it to the Clojure Dev Group. Generally your idea will be best received when there are supporting artifacts such as "Here's a great use case where it demonstrates value" and "Here's some discussion where other people desire the same value proposition".
As background re the collection model: http://insideclojure.org/2016/03/16/collections/
Keys and vals are both defined as functions that take a seqable of map entries. Given that definition, it is not easily possible to widen it to also take a collection with the associative trait, because a collection like vector will not produce a seq of entries, but rather a seq of vector values. The ability to seq to map entries is something only provided by maps.
Yes, it would be possible to make a function that worked on either, but I think doing so would break the existing contract of these functions, not extend it.
To the design question of whether it should have been done this way or not in the first place, that's harder for me to say.
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