The standard does not and should not tell you the complexity of functions like gethash
. Imagine if it did do that: this would constrain implementations of the language to use implementations of the functions which agreed with the complexity in the standard. If someone came up with a much better hashing function, then implementations could not use it.
Well, you could argue, that's silly: the standard merely needs to specify upper bounds on complexity. This would allow an implementation to use any better function that it liked, right? But that's also not an answer: there could be (and in many cases are) algorithms which have terrible worst-case performance but much better expected performance. To deal with that in the standard would either be impossible (I think) or would cause it to be entirely covered by complicated descriptions of what complexity was acceptable and when and when not.
Providing upper complexity bounds would also rule out implementations which wanted to make tradeoffs between (say) how complicated and large the implementation is and how performant it is in some cases: an implementation should be allowed to have hashtables which are, inside, alists, for instance: these are typically very fast for small numbers of keys, but their performance falls apart for large numbers of keys. Such an implementation should be allowed.
There are some cases where the complexity of things is kind of obvious from the standard: it seems to be clear that the time complexity of length
is linear in the length of the list (except that it may not terminate if the list is circular). But that's not true: there's nothing to prevent an implementation maintaining a length value somewhere which would make length
be constant time in some cases. This would obviously be a heroic (to the point of being implementationally-implausible I think) and useless optimisation, but it is not the place of the standard to rule it out.
As an example of a case where a language (not an implementation of CL!) does something like this consider this description of Racket's list?
predicate:
Returns #t
if v is a list: either the empty list, or a pair whose second element is a list. This procedure effectively takes constant time due to internal caching (so that any necessary traversals of pairs can in principle count as an extra cost of allocating the pairs).
I don't completely understand this but I think that Racket must implementationally have a flag in its cons object which tells you that its cdr is a proper list, and then relies on immutability to know that is this is ever true it's always true. If CL had such a function then it would be very much harder to make it run in constant time.