With-fast-alist
(with-fast-alist name form) causes name to be a fast alist
for the execution of form.
Logically, with-fast-alist just returns form.
Under the hood, we cause alist to become a fast alist before executing
form. If doing so caused us to introduce a new hash table, the hash
table is automatically freed after form completes.
More accurately, under the hood (with-fast-alist name form)
essentially expands to something like:
(if (already-fast-alist-p name)
form
(let ((<temp> (make-fast-alist name)))
(prog1 form
(fast-alist-free <temp>))))
Practically speaking, with-fast-alist is frequently a better choice
then just using make-fast-alist, and is particularly useful for
writing functions that can take either fast or slow alists as arguments. That
is, consider the difference between:
(defun bad (alist ...)
(let* ((fast-alist (make-fast-alist alist))
(answer (expensive-computation fast-alist ...)))
(prog2$ (fast-alist-free fast-alist)
answer)))
(defun good (alist ...)
(with-fast-alist alist
(expensive-computation alist ...)))
Either approach is fine if the caller provides a slow alist. But if
the input alist is already fast, bad will (perhaps unexpectedly)
free it! On the other hand, good is able to take advantage of an
already-fast argument and will not cause it to be inadvertently freed.
See also the macro with-fast-alists defined in the community book
"books/centaur/misc/hons-extra.lisp", which allows you to call with-fast-alist on several alists simultaneously.
The community book "books/centaur/misc/hons-extra.lisp" extends the
b* macro with the with-fast pattern binder. That is, after
executing (include-book "centaur/misc/hons-extra.lisp" :dir :system)
you may write something like this:
(b* (...
((with-fast a b c ...))
...)
...)
which causes a, b, and c to become fast alists until the
completion of the b* form.
Note that with-fast-alist will cause logically tail-recursive
functions not to execute tail-recursively if its cleanup phase happens after
the tail-recursive call returns.