Should (get vector n) work?

I’ve been paying with hash-maps that have keys that are not keywords for the first time
e.g.

(get fred [1 2 3 4])

I simplified to using integers as keys: (get bert 1)
then I tried when bert is a vector instead of a map and it worked, like (nth bert 1) would. I looked at (doc get). It says (get) requires a map but clearly Clojure handles vector gets too.
I’ve seen an earlier question that points out that (get) is more forgiving of ‘out of range’ values, returning nil rather than an error.
Is the documentation incomplete?

The ClojureDocs.org site is not official documentation, but very often contains extra information that one finds useful about the behavior of Clojure built-in functions, including get: https://clojuredocs.org/clojure.core/get

There have been many questions and conversations on the built-in Clojure doc strings, being incomplete, terse, and many other similar adjectives. The fact that the first arg to get is named map is certainly not conducive to easy discoverability that it works on vectors, too, and one could propose a change to that doc string suggesting that this possibility be spelled out explicitly in the doc string. https://ask.clojure.org is the place to suggest such things, if you want the Clojure maintainers to consider making such a change (understand that the results of such consideration might well be “sorry, but no”).

TL;DR: yes, get should work on a vector because it works on types that support lookup, roughly “associative types”. Here’s how we can figure that out…

(source get) shows that it calls clojure.lang.RT/get and if we look at that Java code:

static public Object get(Object coll, Object key){
    if(coll instanceof ILookup)
        return ((ILookup) coll).valAt(key);
    return getFrom(coll, key);
}

If we look at (ancestors (type [1 2 3 4])) we’ll see clojure.lang.ILookup in there, so vector implements ILookup and therefore (get my-vector 1) will effectively call (.valAt my-vector 1).

If we look at the source of clojure.lang.APersistentVector/valAt we see that calls (.valAt my-vector 1 nil) (i.e., with a null not found argument) and that is:

public Object valAt(Object key, Object notFound){
    if(Util.isInteger(key))
        {
        int i = ((Number) key).intValue();
        if(i >= 0 && i < count())
            return nth(i);
        }
    return notFound;
}

And here we see that it calls nth under the covers.

That also explains why (get [1 2 3 4] :a) returns nil rather than an error: because :a fails the Util.isInteger() test and so the notFound value – nil in this case – is returned.

It also shows that while (nth [1 2 3 4] :a) and (nth [1 2 3 4] 10) both throw exceptions, using get instead produces nil in both cases.

3 Likes

I’m sorry to nitpick but this is false. The docstring for get is as follows:

“Returns the value mapped to key, not-found or nil if key not present.”

Notice “value mapped to key”. get requires a mapping, not a hash-map. I agree that this could be more clear. (In my experience the core maintainers have an extremely high bar for changing the docstring of such a central function, and so I would not expect clarification there to be forthcoming. Your best bet to improve clarity on this point is to contribute to clojuredocs’ get page or somewhere on clojure.org.)

What’s going on here is that vectors are associative structures (see associative?), meaning they provide a mapping from index to value. So getting a value from a hash-map looks up the value mapped to (associated with) the given key, whereas getting a value from a vector looks up the value at (associated with) the given index.

1 Like

Thanks everyone. I think

sums up my experience as a Clojure noob. I didn’t expect a simple documentation string to tell me everything but I didn’t expect it to say, or even imply by parameter name, anything that was misleading.

I didn’t know that https://ask.clojure.org/ was the place to raise issues, so thanks for that too. It’s not obvious, even once you are there. I have half a page of Clojure bookmarks but that wasn’t on it.

2 Likes

Dave, I think he is referring to the fact that the first parameter of get that you see when you type (doc get) is named map.

2 Likes

D’oh! Sorry to miss that. You’re both right, that’s misleading.

I think I shall report this documentation ‘feature’. Clojure usually addresses abstractions and as @seancorfield reminded me, there is an ‘associative’ abstraction. I think the example should refer to that rather than to a map.

Done!