In Common Lisp it is idiomatic for functions to allow their behavior to be overridden with keyword arguments.

So Lispers often write functions whose sole purpose is to call a function that takes keyword arguments, but with different defaults.

Since keyword arguments are processed from left to right, and the leftmost occurrence takes precedence, this is easy to do:

(hash-table-test (make-hash-table)) => EQL
(defun make-equal-hash-table (&rest args &key &allow-other-keys)
  (apply #'make-hash-table :test 'equal args))
(hash-table-test (make-equal-hash-table)) => EQUAL

The downside is that the new default is fixed: the caller cannot override it.

(hash-table-test (make-equal-hash-table :test 'eql)) => EQUAL

Can we change the default, but still allow the new default to be overridden? Of course. The obvious way is to append the new default to the end of the argument list:

(defun make-equal-hash-table-v2 (&rest args &key &allow-other-keys)
  (apply #'make-hash-table (append args '(:test equal))))
(hash-table-test (make-equal-hash-table-v2)) => EQUAL
(hash-table-test (make-equal-hash-table-v2 :test 'eql)) => EQL

Unfortunately this allocates a new list. Even SBCL is not smart enough to eliminate the allocation, as you can see from (disassemble 'make-hash-table-v2):

; DE5:       488B3DA4FEFFFF   MOV RDI, [RIP-348]              ; '(:TEST EQUAL)
; DEC:       488B05A5FEFFFF   MOV RAX, [RIP-347]              ; #<SB-KERNEL:FDEFN SB-IMPL::APPEND2>

Of course you may not care about such minor allocations. But for situations where you do care, there is another way — one that involves no allocation.

To capture multiple values in Common Lisp, we generally use multiple-value-bind:

(multiple-value-bind (x y z) (values 1 2 3)
  (+ x y z))
=> 6

But multiple-value-bind is just a macro; the actual special form that allows capturing multiple values is the much less frequently encountered multiple-value-call. You could rewrite the above to use multiple-value-call directly:

(multiple-value-call (lambda (&optional x y z)
                       (+ x y z))
  (values 1 2 3))
=> 6

But multiple-value-call is more flexible than that, because it captures all the values returned from all the forms that are provided to it as arguments:

(multiple-value-call (lambda (&optional x y z)
                       (+ x y z))
  (values 1) (values 2 3))
=> 6
(multiple-value-call (lambda (&optional x y z)
                       (+ x y z))
  (values 1 2) 3)
=> 6
(multiple-value-call (lambda (&optional x y z)
                       (+ x y z))
  1 2 3)
=> 6

And this takes place entirely on the stack, without any allocations.

To complete the trick, besides multiple-value-call, we need one other function, values-list, which takes a list and returns it as multiple values:

(values-list '(1 2 3))
=> 1, 2, 3

And now, with multiple-value-call and values-list, we have everything we need to write wrapper functions that allow their new defaults to be overridden, without any allocation:


(defun make-equal-hash-table-v3 (&rest args &key &allow-other-keys)
  (multiple-value-call #'make-hash-table
    (values-list args)
    (values :test 'equal)))
(hash-table-test (make-equal-hash-table-v3)) => EQUAL
(hash-table-test (make-equal-hash-table-v3 :test 'eql)) => EQL

(To understand how the argument list is constructed, watch what happens when multiple-value-calling list:

(multiple-value-call #'list (values-list '(:test eql)) :test 'equal)
=> (:TEST EQL :TEST EQUAL)

)