From: Kalamaro
Subject: How can i make "case" to use equal?
Date: 
Message-ID: <9n2ga3$4ng56$1@ID-91322.news.dfncis.de>
"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.

how can i do it?

From: Kent M Pitman
Subject: Re: How can i make "case" to use equal?
Date: 
Message-ID: <sfwelpnjchl.fsf@world.std.com>
"Kalamaro" <·····@usuarios.retecal.es> writes:

> 
> "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)
                         ^
                        Do not put a space here.
                        It may work in some implementations, but not in all.
 
> 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.
> 
> how can i do it?

":test" is one token.  There must be no space between the ":" and the "test".

:test is a keyword.  Keywords in Lisp have a prefix colon followed directly
by the symbol name, a name in the KEYWORD package.  It's the same as a
symbol like CL:CAR or KEYWORD:TEST.  It *is* a symbol in the KEYWORD package,
in fact, so you could actually say KEYWORD:TEST there, but :TEST is the
accepted way to write that.  NO ONE wites out KEYWORD:xxx when denoting 
keywords; it's bad style.
From: Kent M Pitman
Subject: Re: How can i make "case" to use equal?
Date: 
Message-ID: <sfwd757jbsd.fsf@world.std.com>
"Kalamaro" <·····@usuarios.retecal.es> writes:

> "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.
> 
> how can i do it?

OOPS.  I misread your question.  Lo siento.  Let me try again...

CASE doesn't have a variant that uses EQUAL.  You'd have to write it 
yourself.

e.g., you want something like:

 (defmacro case-equal (exp &body clauses)
   (let ((temp (gensym)))
    `(let ((,temp ,exp))
       (cond ,@(mapcar #'(lambda (clause)
			   (destructuring-bind (keys . clause-forms) clause
			     (cond ((eq keys 'otherwise)
				    `(t ,@clause-forms))
				   (t
				    (if (atom keys) (setq keys (list keys)))
				    `((member ,temp ',keys :test #'equal)
				      ,@clause-forms)))))
		       clauses)))))

This defines a CASE-EQUAL macro like the system's CASE macro but that
uses EQUAL.  You can use Lisp to see the expansion if you like:

 (pprint (macroexpand-1 '(case-equal '(a b)
                           (((a b) (c d)) 3)
                           (otherwise 4))))

 (LET ((#:G5219 '(A B)))
   (COND ((MEMBER #:G5219 '((A B) (C D)) :TEST #'EQUAL)
          3)
         (T 4)))

If you want to supply an arbitrary predicate, you'll need to make the
macro more general.  e.g.,  the following should work, though I didn't
take time to test it:

 (defmacro case-using (pred-exp exp &body clauses)
   (let ((temp (gensym)) (pred (gensym "PRED")))
    `(let ((,pred ,pred-exp) (,temp ,exp))
       (cond ,@(mapcar #'(lambda (clause)
			   (destructuring-bind (keys . clause-forms) clause
			     (cond ((eq keys 'otherwise)
				    `(t ,@clause-forms))
				   (t
				    (if (atom keys) (setq keys (list keys)))
				    `((member ,temp ',keys :test ,pred)
				      ,@clause-forms)))))
		       clauses)))))

 (case-using #'equal '(a b)
   (((a b) (c d)) 3)
   (otherwise 4))
 => 3
From: Erik Naggum
Subject: Re: How can i make "case" to use equal?
Date: 
Message-ID: <3208606982556119@naggum.net>
* "Kalamaro" <·····@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.

///
From: Espen Vestre
Subject: Re: How can i make "case" to use equal?
Date: 
Message-ID: <w6n148hdx6.fsf@wallace.ws.nextra.no>
The need for an "equal case" has struck me several times, are there anybody
here who regularily uses a macro for this? A series of if-then-else tests for
equality of strings is pretty common, and I think that a CASE version of
such tests is much more readable than a COND (or IF*!) version.
-- 
  (espen)
From: Sam Steingold
Subject: Re: How can i make "case" to use equal?
Date: 
Message-ID: <ud754uwc9.fsf@xchange.com>
> * In message <··············@wallace.ws.nextra.no>
> * On the subject of "Re: How can i make "case" to use equal?"
> * Sent on 06 Sep 2001 11:22:13 +0200
> * Honorable Espen Vestre <·····@*do-not-spam-me*.vestre.net> writes:
>
> The need for an "equal case" has struck me several times, are there anybody
> here who regularily uses a macro for this? A series of if-then-else tests for
> equality of strings is pretty common, and I think that a CASE version of
> such tests is much more readable than a COND (or IF*!) version.

(defun case-expand (form-name test keyform clauses)
  (let ((var (gensym (format nil "~a-KEY-" form-name))))
    `(let ((,var ,keyform))
      (cond
        ,@(maplist
           #'(lambda (remaining-clauses)
               (let ((clause (first remaining-clauses))
                     (remaining-clauses (rest remaining-clauses)))
                 (unless (consp clause)
                   (error "~S: missing key list" form-name))
                 (let ((keys (first clause)))
                   `(,(cond ((or (eq keys 't) (eq keys 'otherwise))
                             (if remaining-clauses
                                 (error "~S: the ~S clause must be the last one"
                                        form-name keys)
                                 't))
                            ((listp keys)
                             `(and ,@(mapcar #'(lambda (key)
                                                 `(,test ,var ',key))
                                             keys)))
                            (t `(,test ,var ',keys)))
                     ,@(rest clause)))))
           clauses)))))

(defmacro fcase (test keyform &body clauses)
  (case-expand 'fcase test keyform clauses))

> (macroexpand '(fcase equalp foo ("bar" 1) (("baz" "zot") 2) (otherwise 3)))

(let ((#:FCASE-KEY-1452 foo))
 (cond ((equalp #:FCASE-KEY-1452 '"bar") 1)
  ((and (equalp #:FCASE-KEY-1452 '"baz") (equalp #:FCASE-KEY-1452 '"zot")) 2)
  (t 3))) ;
t

-- 
Sam Steingold (http://www.podval.org/~sds)
Support Israel's right to defend herself! <http://www.i-charity.com/go/israel>
Read what the Arab leaders say to their people on <http://www.memri.org/>
I want Tamagochi! -- What for?  Your pet hamster is still alive!
From: Sam Steingold
Subject: Re: How can i make "case" to use equal?
Date: 
Message-ID: <u4rqguv9a.fsf@xchange.com>
> * In message <··············@wallace.ws.nextra.no>
> * On the subject of "Re: How can i make "case" to use equal?"
> * Sent on 06 Sep 2001 11:22:13 +0200
> * Honorable Espen Vestre <·····@*do-not-spam-me*.vestre.net> writes:
>
> The need for an "equal case" has struck me several times, are there anybody
> here who regularily uses a macro for this? A series of if-then-else tests for
> equality of strings is pretty common, and I think that a CASE version of
> such tests is much more readable than a COND (or IF*!) version.

(defun case-expand (form-name test keyform clauses)
  (let ((var (gensym (format nil "~a-KEY-" form-name))))
    `(let ((,var ,keyform))
      (cond
        ,@(maplist
           #'(lambda (remaining-clauses)
               (let ((clause (first remaining-clauses))
                     (remaining-clauses (rest remaining-clauses)))
                 (unless (consp clause)
                   (error "~S: missing key list" form-name))
                 (let ((keys (first clause)))
                   `(,(cond ((or (eq keys 't) (eq keys 'otherwise))
                             (if remaining-clauses
                                 (error "~S: the ~S clause must be the last one"
                                        form-name keys)
                                 't))
                            ((listp keys)
                             `(or ,@(mapcar #'(lambda (key)
                                                `(,test ,var ',key))
                                            keys)))
                            (t `(,test ,var ',keys)))
                     ,@(rest clause)))))
           clauses)))))

(defmacro fcase (test keyform &body clauses)
  (case-expand 'fcase test keyform clauses))

> (macroexpand '(fcase equalp foo ("bar" 1) (("baz" "zot") 2) (otherwise 3)))

(let ((#:FCASE-KEY-1452 foo))
 (cond ((equalp #:FCASE-KEY-1452 '"bar") 1)
  ((or (equalp #:FCASE-KEY-1452 '"baz") (equalp #:FCASE-KEY-1452 '"zot")) 2)
  (t 3))) ;
t

[cancelled and re-posted due to a typo in code]

-- 
Sam Steingold (http://www.podval.org/~sds)
Support Israel's right to defend herself! <http://www.i-charity.com/go/israel>
Read what the Arab leaders say to their people on <http://www.memri.org/>
In C you can make mistakes, while in C++ you can also inherit them!
From: Erik Naggum
Subject: Re: How can i make "case" to use equal?
Date: 
Message-ID: <3208789962759722@naggum.net>
* Sam Steingold <···@gnu.org>
> (defun case-expand (form-name test keyform clauses)
>   ...)
> 
> (defmacro fcase (test keyform &body clauses)
>   (case-expand 'fcase test keyform clauses))

  Neat, but one of the really good things about case and the like is that
  they can be optimized into jump tables and the like since the keys are
  all constants at compile-time.  Expanding into cond is not a good idea if
  you want to keep this.  In fact, using a hash table for the keys allows
  you to convert the keys into small integers.

///
From: Thomas F. Burdick
Subject: Re: How can i make "case" to use equal?
Date: 
Message-ID: <xcv7kvcp0vl.fsf@conquest.OCF.Berkeley.EDU>
Erik Naggum <····@naggum.net> writes:

> * Sam Steingold <···@gnu.org>
> > (defun case-expand (form-name test keyform clauses)
> >   ...)
> > 
> > (defmacro fcase (test keyform &body clauses)
> >   (case-expand 'fcase test keyform clauses))
> 
>   Neat, but one of the really good things about case and the like is that
>   they can be optimized into jump tables and the like since the keys are
>   all constants at compile-time.  Expanding into cond is not a good idea if
>   you want to keep this.  In fact, using a hash table for the keys allows
>   you to convert the keys into small integers.

The one thing I would have done differently from your version would be
to special-case EQL to make it expand into a normal CASE form.  I'm
guessing that would give better performance for things like 
  (case x 
    (0 ...)
    (1 ...)
    (2 ...)
    ...
    (15 ...))
where you've already told the compiler that X is [0, 15].
From: Kent M Pitman
Subject: Re: How can i make "case" to use equal?
Date: 
Message-ID: <sfwsne0ozj8.fsf@world.std.com>
···@conquest.OCF.Berkeley.EDU (Thomas F. Burdick) writes:

> Erik Naggum <····@naggum.net> writes:
> 
> > * Sam Steingold <···@gnu.org>
> > > (defun case-expand (form-name test keyform clauses)
> > >   ...)
> > > 
> > > (defmacro fcase (test keyform &body clauses)
> > >   (case-expand 'fcase test keyform clauses))
> > 
> >   Neat, but one of the really good things about case and the like is that
> >   they can be optimized into jump tables and the like since the keys are
> >   all constants at compile-time.  Expanding into cond is not a good idea if
> >   you want to keep this.  In fact, using a hash table for the keys allows
> >   you to convert the keys into small integers.
> 
> The one thing I would have done differently from your version would be
> to special-case EQL to make it expand into a normal CASE form.  I'm
> guessing that would give better performance for things like 
>   (case x 
>     (0 ...)
>     (1 ...)
>     (2 ...)
>     ...
>     (15 ...))
> where you've already told the compiler that X is [0, 15].

I would leave it to the compiler to know what can be special-cased and
whatcan't.  It might be that things can be optimized for reasons invisible
to you.  For example, the compiler might have special knowledge that a pointer
will not change, and might use binary search on the literal pointer to
select among the elements quickly. (In some cases it might get help from the
loader.) (Or, at least, it's allowed to do this.  All you know is that you're
not allowed to know.)

The reason we have standard idioms is so that compiler writers can spend a lot
of time trying to figure out how to hyper-optimize them.  The more we turn
these into low-level equivalents (e.g., writing our own LOOP because we don't
like the system one) the more we thwart the ability of the system to do things
we can't know about that make things better.
From: Erik Naggum
Subject: Re: How can i make "case" to use equal?
Date: 
Message-ID: <3208796186377652@naggum.net>
* Thomas F. Burdick
> The one thing I would have done differently from your version would be
> to special-case EQL to make it expand into a normal CASE form.

  It would be a wasted gethash call, but otherwise no different at all.
  So I am not sure what you think would be so different.

> I'm guessing that would give better performance for things like
>   (case x 
>     (0 ...)
>     (1 ...)
>     (2 ...)
>     ...
>     (15 ...))
> where you've already told the compiler that X is [0, 15].

  Nothing would happen to these keys, and the case would do exactly the
  same as without my code.

  If you did not specifically refer to my code but replied to my article,
  anyway, ignore this.  Replying to a slightly different article than you
  appear to is normal on USENET, but it is still a little confusing.

///