5.10 Dynamic Tuples

Tuples, like maps, are collections of key/value pairs updated functionally. But where maps are designed for situations where the key values are an open class — they’re created at runtime, there can be arbitrarily many of them, and you don’t know in advance what they’re going to be — tuples are designed for the case where the keys are a closed class: you know what they are at compile time. Also, in a map, the range values are all logically of the same type, while in a tuple, each key has its own value type.

Perhaps the best analogy is that a tuple is like a row in a relational table. Each key corresponds to a relational column, and of course the columns can contain different types. You could also think of tuples as like objects where the keys correspond to slots, but at least for now, tuples have no notion of class; there’s no way to write methods that dispatch on a type field, except manually using good old ecase or the like. So I think the relational row — a heterogeneous collection of keyed values without associated behaviors — is the clearest analog.

Since Lisp is untyped, you can, of course, use a map for this purpose anyway; in a statically typed language you’d have to give all the column types some common supertype, but Lisp spares you that ceremony. So a map is a viable choice, even when the values are of different types. Nonetheless, there are three reasons you might want to use a tuple rather than a map:

Dynamic tuples are implemented quite unlike any other FSet type. Each key is assigned a number. Each descriptor contains a vector of pairs of integers, implemented as bytes (bitfields) of a fixnum; one integer is the key number, and the other is the index of the value in tuples using this descriptor. Lookup is done by linear search through the vector for a pair whose key number matches the key being looked for. For small tuples, cache effects make this quite fast. For large ones (more than eight keys), FSet does something clever: it dynamically reorders the descriptor vector so that frequently-used keys tend to migrate toward the front. If the frequency of access of the different keys follows something like a power-law distribution, with some keys being accessed much more frequently than others — not an unreasonable assumption — then searches for keys beyond the first eight or so will be relatively rare.