Subject: Re: How best to intern arbitrary names/keywords
From: rpw3@rpw3.org (Rob Warnock)
Date: Sun, 20 Aug 2006 05:45:59 -0500
Newsgroups: comp.lang.lisp
Message-ID: <b6KdnQDrrc_6oHXZnZ2dnUVZ_vudnZ2d@speakeasy.net>
jmckitrick <j_mckitrick@yahoo.com> wrote:
+---------------
| Barry Margolin wrote:
| > Why not just do string comparisons:
| 
| That's what I did originally.  I'm experimenting right now.  Trying out
| other solutions, some to learn, and others to see if they add
| abstraction/remove redundancy.  I don't like to use string comparisons
| in CL, if it can be avoided.  I save those for JavaScript.  :-)
+---------------

It sounds very much like you're doing premature optimization
[which some say is the root of all programming evil, but that's
another story]. Do you really *KNOW* that a simple COND using
string comparisons is the main bottleneck in your application?
If not, forget your "likes" and just use it. This is especially
true if the number of distinct strings you have to deal with is
small and/or the average length is small.

If you just can't abide that [for whatever reason], and the
number of distinct strings is both small and *fixed* in your
application [you shouldn't be using CASE for data-driven
dispatch unless the number of distinct cases is fixed] then
using (CASE (INTERN (STRING-UPCASE db-field) :KEYWORD) ...cases...)
is certainly reasonable. That is:

    (case (intern (string-upcase db-field) :keyword)
      ((:foo)  (handle-a-foo))
      ((:bar)  (handle-a-bar))
      ((:quux) (handle-a-quux))
      (otherwise (handle-default-or-error db-field)))

A variant on that which is much more extensible is to make the
code executed by each branch of the CASE be a generic function
that uses an EQL specializer with a keyword value.

    (defmethod db-field-dispatch (key)
      ...code to handle input without an explicit handler...)

    (defmethod db-field-dispatch ((key (eql :foo)))
      (declare (ignore key))
      ...code to handle a "foo"...)

    (defmethod db-field-dispatch ((key (eql :bar)))
      (declare (ignore key))
      ...code to handle a "bar"...)

    (defmethod db-field-dispatch ((key (eql :quux)))
      (declare (ignore key))
      ...code to handle a "quux"...)

    ...other cases...

    ;; Call with:
    (db-field-dispatch (intern (string-upcase db-field) :keyword))

But if the number of cases is very large or needs to be even more
easily extensible, then what you probably want to do instead is
have an #'EQUAL hashtable that maps directly from an input string
to the function to handle that string, which you would call like
this, assuming you *didn't* need to pass the DB-FIELD to any of
the functions except the default/error handler.

    (flet ((default () (db-default-or-error db-field)))
      (funcall (gethash db-field *db-field-functions* #'default)))

Of course, if you changed the protocol so that you *always* pass
the DB-FIELD to all the functions [which you might want to do
anyway so you can have functions which handle multiple string
values], then it's a bit simpler:

    (funcall (gethash db-field *db-field-functions* #'db-default-or-error)
	     db-field)


-Rob

-----
Rob Warnock			<rpw3@rpw3.org>
627 26th Avenue			<URL:http://rpw3.org/>
San Mateo, CA 94403		(650)572-2607