Subject: Re: How can i make "case" to use equal? From: Erik Naggum <erik@naggum.net> Date: Tue, 04 Sep 2001 15:43:06 GMT Newsgroups: comp.lang.lisp Message-ID: <3208606982556119@naggum.net> * "Kalamaro" <feman@usuarios.retecal.es> > "case", as well as "member", uses eql to compare the forms. When you use > "member", you can use "equal" by putting > > (member '(this) these : test #'equal) > > I'm trying to do the same in "case" clauses. I've tryed putting ": test > #'equal" in several places of the "case" clause but it didn't work. Nothing beats reading the specification to find out how such things work. A copy of the standard or something very much like it almost certainly comes with your Common Lisp implementation or it provides pointers to resources on the Net. I would suggest you look for the documentation available with your Common Lisp system. However, I trust that you will look for the documentatio, so this sketchy answer will be valuable in context of what you find in the documentation. You are quite right that case, ccase and ecase all use eql for the test and you cannot change this. This is because the cases are specified as literals in the source of your program, quite unlike the elements of a sequence searched by member and the like at run-time. It is expected that the compiler make good use of the fact that these are literals. You find the same "restriction" in all other language that have a case-like branching form. However, it would be in the Common Lisp spirit to make a more general form available without cost to the programmer, although it would be a little more expensive to implement. The key to implement a more general form is that we should keep the very valuable optimization quality of testing for identity. There are several ways to accomplish this, but I prefer the following: (defun extract-case-keys (case-form) (loop for clause in (cddr case-form) until (and (eq (car case-form) 'case) (member (car clause) '(t otherwise))) if (listp (car clause)) append (car clause) else collect (car clause))) ;; export if packaged (defparameter *with-hashed-identity-body-forms* '((case . extract-case-keys) (ccase . extract-case-keys) (ecase . extract-case-keys)) "Alist of the valid operators in body forms of a with-hashed-identity form with their key-extraction function.") (defun with-hashed-identity-error (body) (error "Body form of with-hashed-identity is ~A, but must be one of:~{ ~A~}." (caar body) (mapcar #'car *with-hashed-identity-body-forms*))) (defun with-hashed-identity-hashtable (hash-table body) (dolist (key (funcall (or (cdr (assoc (car body) *with-hashed-identity-body-forms*)) 'with-hashed-identity-error) body)) (setf (gethash key hash-table) key)) hash-table) ;; export if packaged (defmacro with-hashed-identity (hash-options &body body) "A wrapper around case forms to enable case tests via a hashtable." (unless (and (listp (car body)) (null (cdr body))) ;TODO: Allow multiple body forms. (error "Body of with-hashed-identity must be a single form.") (let ((hash-table (make-symbol "hashtable"))) `(let ((,hash-table (load-time-value (with-hashed-identity-hashtable (make-hash-table ,@hash-options) ',(car body))))) (,(caar body) (gethash ,(cadar body) ,hash-table) ,@(cddar body))))) This allows the following forms to succeed: (with-hashed-identity (:test #'equal) (case "foo" ("foo" 'yeah) (t 'bummer))) (with-hashed-identity (:test #'equalp) (case "foo" ("FOO" 'yeah) (t 'bummer))) (with-hashed-identity (:test #'equalp) (case (vector #\f #\o #\o) (#(#\F #\O #\O) 'yeah) (t 'bummer))) Style hint: The keyword is written :test. An intervening space should signal a reader error, whether the Common Lisp reader or a human reader. By the way, this is the kind of macros I write when I want to enhance the language to do more than it is specified to do. The difference between my style and that of those with, say, 23 years of experience may be because I studied Common Lisp when it was being standardized and for all practical purposese had reached its "final form", as opposed to learning "Lisp" during the horrible mess that preceded all standardization efforts two decades earlier. Sometimes, it is very beneficial to start studying something only in its mature state. For instance, the Perl code written by people who start with Perl 5.6 is vastly superior to that of those who started with Perl 4 or even earlier versions. Similarly, those who start with Java or C++ now are probably going to write vastly superior code to those who have been tagged along on its evolutionary path. I think my luck in timing is that I have arrived on the scene when most of the work has been done to build the infrastructure and I have been able to build on top of a solid foundation I could trust, as opposed to having lived with a "fluidity" that caused trusting the foundation to be very risky, indeed. However, more than half a decade has passed since Common Lisp solidified, so those who once could not trust the standard should have had ample time to adjust by now. ///