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.

///