In Common Lisp standard, a hash table can use only these test functions to check keys equality: eq, eql, equal and equalp (https://eli.thegreenplace.net/2004/08/08/equality-in-lisp), however sometimes you might want to provide a custom comparison and hash functions.
Some implementations support this by providing an extension. Cl-custom-hash-table provides a portability layer for defining such custom hash functions.
BTW, did you know there is a table of Common Lisp portability layers?
Working on this post, I've created an issue to add cl-custom-hash-table into this list.
Let's see, how does SBCL allow you to define custom hash functions using sb-ext:define-hash-table-test macro.
For example, we want a hash table where we can use the first character of a string, given as a key.
First, we need to define a comparison function:
POFTHEDAY> (defun first-char-equal (left right) (char-equal (elt left 0) (elt right 0))) FIRST-CHAR-EQUAL POFTHEDAY> (first-char-equal "Foo" "foo") T POFTHEDAY> (first-char-equal "Foo" "Bar") NIL
Please, note, here I've sacrificed the processing of corner cases when left and right are empty or not a strings in favor of readability.
Also, we need to define a hash-function:
POFTHEDAY> (defun first-char-hash (text) (sxhash (char-downcase (elt text 0)))) FIRST-CHAR-HASH POFTHEDAY> (first-char-hash "foo") 1193941380876393778 (61 bits, #x1091BB5C31C6DD32) POFTHEDAY> (first-char-hash "Foo") 1193941380876393778 (61 bits, #x1091BB5C31C6DD32)
Now we can to register our custom hash function and to create a hash-table:
POFTHEDAY> (sb-ext:define-hash-table-test first-char-equal first-char-hash) FIRST-CHAR-EQUAL POFTHEDAY> (defparameter *h* (make-hash-table :test 'first-char-equal)) *H* POFTHEDAY> (setf (gethash "Foo" *h*) :BAR) :BAR POFTHEDAY> (setf (gethash "fooga-dooga" *h*) :ANOTHER) :ANOTHER POFTHEDAY> (setf (gethash "second" *h*) :VALUE) :VALUE POFTHEDAY> (maphash (lambda (key value) (format t "~A -> ~A~%" key value)) *h*) Foo -> ANOTHER second -> VALUE
As you can see, this works as expected.
Now let's try to define the same hash table using cl-custom-hash-table:
POFTHEDAY> (cl-custom-hash-table:define-custom-hash-table-constructor make-first-char-hash :test first-char-equal :hash-function first-char-hash) MAKE-FIRST-CHAR-HASH POFTHEDAY> (defparameter *h* (make-first-char-hash)) *H* POFTHEDAY> (cl-custom-hash-table:with-custom-hash-table (setf (gethash "Foo" *h*) :BAR) (setf (gethash "fooga-dooga" *h*) :ANOTHER) (setf (gethash "second" *h*) :VALUE) (maphash (lambda (key value) (format t "~A -> ~A~%" key value)) *h*)) Foo -> ANOTHER second -> VALUE
As you can see, the result is the same. However, we have to wrap our code into the cl-custom-hash-table:with-custom-hash-table macro call. We need this to ensure that correct functions will be used on Lisp implementation which doesn't support the hash function's customization.