4.6 Map and Seq Defaults

FSet maps and seqs both allow you to specify the value that will be returned by a lookup when the key or index is outside the domain of the collection. This is a handy feature that significantly improves the elegance of certain kinds of code: you specify the default once, when creating the collection, rather than every place you access it. Contrast a Lisp alist, where every access would look like (or (cdr (assoc x alist)) default), or even a CL hashtable, where you’d have to supply the default as the third argument to every call to gethash.

For convenience, and consistency with CL, the default default — the value used as a default if you don’t specify one — is good old nil. In a few cases, however, such as compose of a map with a function, the presence of any default is annoying, because the function being composed with has to accept the default value and produce a default for the result map. So FSet also allows maps and seqs to have no default. When this option is used, lookup on the collection, given a key or index outside its domain, will signal an error. You can use this as a convenient defensive programming mechanism in cases where you don’t expect out-of-domain lookups.

When a map or seq is printed that has a default other than nil, the default is printed at the end, separated by a slash; when it has no default, the string /[no default] is appended.

Map defaults are handy when you’re using chained maps, as discussed in the previous section, since they let you arrange for the inner maps to be created automatically as needed. This is done by giving the outer map a default which is the empty map. Then you can use the setf expression shown in the previous section without further ado:

(let ((m (empty-map :default (empty-map))))
  ...
  (setf (@ (@ m k1) k2) value)
  ...)

If you were doing something like this with CL hash tables, you would have to explicitly check, before doing the setf, that the outer map had an entry for k1, and if not, initialize it to the empty map; roughly:

(mvlet ((inner found? (gethash k1 m)))
  (unless found?
    (setf inner (make-hash-table))
    (setf (gethash k1 m) inner))
  (setf (gethash k2 inner) value))

With FSet, the default on the outer map makes the explicit check unnecessary; the empty map is used automatically if m has no entry yet for k1.