From: ·····················@gmail.com
Subject: Implications of a definition of STRING-CASE
Date: 
Message-ID: <1188215708.029906.88110@22g2000hsm.googlegroups.com>
Hello,

I wrote a simple macro STRING-CASE, which is supposed to be equivalent
to case but testing for strings instead. I'm worried though of the
implications of the way this code is written, It was suggested to me
that I am leaking symbols by using intern in a bad way.
I'd really appreciate some comments regarding this issue and any
others which I may have missed. The code is annotated with
descriptions of what's happening from my point of view, in-case that
helps to clarify any mistakes I made.

;; Case keyform {normal-clause}* [otherwise-clause] => result*

;; String-case should be exactly the same as normal case
;; but it the keys inside normal-clauses are strings
;; and a string= test is done instead of eql.

;; To do this we actually use case itself
;; for example

#||
(string-case x
  ("foo" 1)
  ("bar" 2)
  ("baz" 3))
||#

;; Could be written using case like:

#||
(case (find-symbol x)
  (|foo| 1)
  (|bar| 2)
  (|baz| 3))
||#

;; So an explanation of what happens exactly is in order

;; Case works on symbols and checks EQL,
;; find-symbol returns a symbol for a string
;;   given that its been interned already
;;   otherwise it just returns nil
;; |foo| is simply the symbol with name "foo"
;;   so (eql (find-symbol "foo") '|foo|) is t
;;   the reason for this is that the reader interned |foo|
;;   when it read it.

;; The macro string-case is permitted to emulate this by
;; interning a number of strings, One for each key in the case
keyforms

;; There are 3 cases to consider for case keyforms
;;  * string - Match the string alone
;;  * list of strings - Match any single one of the strings
;;  * the symbol otherwise - always match

(defmacro string-case (keyform &body cases)
  `(case (find-symbol ,keyform)
     ,@(mapcar #'(lambda (case &aux (key (first case))
                                    (body (rest case)))
                   `(,(etypecase key
                        (string (intern key))
                        (list (mapcar #'intern key))
                        (symbol (assert (eql key 'otherwise))
                                'otherwise))
                      ,@body))
               cases)))

;; And some tests because I don't know
;; how else to check if code works

(defun string-case-test-1 (key)
  (string-case key
    ("foo" 'yes)
    (otherwise 'no)))

(defun string-case-test-2 (key)
  (string-case key
    ("quux" 'ok)
    (("foo" "bar") 'yes)
    (otherwise 'no)))

(defun string-case-test-3 (key)
  (string-case key
    (("well") 'yes)
    ("then" key)))


(defun get-test-results ()
  (list (list (string-case-test-1 "foo") 'yes)
        (list (string-case-test-1 "bar") 'no)
        (list (string-case-test-1 "lkjdfjklfdkjldfsa") 'no)

        (list (string-case-test-2 "quux") 'ok)
        (list (string-case-test-2 "foo") 'yes)
        (list (string-case-test-2 "bar") 'yes)
        (list (string-case-test-2 "well") 'no)
        (list (string-case-test-2 "lkjdfjklfdkjldfsa") 'no)

        (list (string-case-test-3 "foo") nil)
        (list (string-case-test-3 "well") 'yes)
        (list (string-case-test-3 "then") "then")
        (list (string-case-test-3 "lkjdfjklfdkjldfsa") nil)))

From: Volkan YAZICI
Subject: Re: Implications of a definition of STRING-CASE
Date: 
Message-ID: <1188221985.549626.5330@d55g2000hsg.googlegroups.com>
Hi,

On Aug 27, 2:55 pm, ·····················@gmail.com wrote:
> I wrote a simple macro STRING-CASE, which is supposed to be equivalent
> to case but testing for strings instead. I'm worried though of the
> implications of the way this code is written, It was suggested to me
> that I am leaking symbols by using intern in a bad way.

As suggested, use STRING= instead. Furthermore, IMHO a more generic
CASE function should be better with a :TEST keyword. For instance,
here is an ad-hoc implementation:

(defmacro extended-case ((item &key (test #'eql) single-test-form)
&body forms)
  "An extended CASE function. \(See CLHS page of CASE function for
details.) If
SINGLE-TEST-FORM is turned on, any (composite or not) form found in
the test
field will be treated as a value returning function/form."
  `(cond
     ,@(mapcar
        #'(lambda (form &aux (test-form (first form)))
            (cons
             (cond
               ((and (not single-test-form) (listp test-form))
                `(member ,item (list ,@test-form) :test ,test))
               ((or (eql 'otherwise test-form)
                    (eql t test-form))
                `t)
               (t `(funcall ,test ,item ,test-form)))
             (rest form)))
        forms)))


Regards.
From: Luís Oliveira
Subject: Re: Implications of a definition of STRING-CASE
Date: 
Message-ID: <m1veazfoqg.fsf@deadspam.com>
Volkan YAZICI <·············@gmail.com> writes:
> As suggested, use STRING= instead. Furthermore, IMHO a more generic
> CASE function should be better with a :TEST keyword. For instance,
> here is an ad-hoc implementation:

Alexandria[1] contains such a function, named SWITCH.

[1] http://common-lisp.net/project/alexandria

-- 
Luís Oliveira
http://student.dei.uc.pt/~lmoliv/
From: Tamas Papp
Subject: Re: Implications of a definition of STRING-CASE
Date: 
Message-ID: <87y7fx2mv8.fsf@pu100877.student.princeton.edu>
·····················@gmail.com writes:

> Hello,
>
> I wrote a simple macro STRING-CASE, which is supposed to be equivalent
> to case but testing for strings instead. I'm worried though of the
> implications of the way this code is written, It was suggested to me
> that I am leaking symbols by using intern in a bad way.
> I'd really appreciate some comments regarding this issue and any
> others which I may have missed. The code is annotated with
> descriptions of what's happening from my point of view, in-case that
> helps to clarify any mistakes I made.
>
> ;; Case keyform {normal-clause}* [otherwise-clause] => result*
>
> ;; String-case should be exactly the same as normal case
> ;; but it the keys inside normal-clauses are strings
> ;; and a string= test is done instead of eql.
>
> ;; To do this we actually use case itself
> ;; for example
>
> #||
> (string-case x
>   ("foo" 1)
>   ("bar" 2)
>   ("baz" 3))
> ||#
>
> ;; Could be written using case like:
>
> #||
> (case (find-symbol x)
>   (|foo| 1)
>   (|bar| 2)
>   (|baz| 3))
> ||#

I would not transform things into symbols, but simply compare them
using string=.

See

http://groups.google.com/group/comp.lang.lisp/browse_thread/thread/562813a92d510073/

on a version with case using equal and modify the macro accordingly.

> ;; And some tests because I don't know
> ;; how else to check if code works

use macroexpand-1 or macroexpand, or the commands of your IDE (eg C-c
RET in SLIME)

HTH,

Tamas
From: Pascal Bourguignon
Subject: Re: Implications of a definition of STRING-CASE
Date: 
Message-ID: <87absd3uis.fsf@thalassa.informatimago.com>
·····················@gmail.com writes:

> Hello,
>
> I wrote a simple macro STRING-CASE, which is supposed to be equivalent
> to case but testing for strings instead. I'm worried though of the
> implications of the way this code is written, It was suggested to me
> that I am leaking symbols by using intern in a bad way.

You're not leaking symbols, but your problem is worse.

> I'd really appreciate some comments regarding this issue and any
> others which I may have missed. The code is annotated with
> descriptions of what's happening from my point of view, in-case that
> helps to clarify any mistakes I made.
>
> ;; Case keyform {normal-clause}* [otherwise-clause] => result*
>
> ;; String-case should be exactly the same as normal case
> ;; but it the keys inside normal-clauses are strings
> ;; and a string= test is done instead of eql.
>
> ;; To do this we actually use case itself
> ;; for example
>
> #||
> (string-case x
>   ("foo" 1)
>   ("bar" 2)
>   ("baz" 3))
> ||#
>
> ;; Could be written using case like:
>
> #||
> (case (find-symbol x)
>   (|foo| 1)
>   (|bar| 2)
>   (|baz| 3))
> ||#
>
> ;; So an explanation of what happens exactly is in order
>
> ;; Case works on symbols and checks EQL,
> ;; find-symbol returns a symbol for a string
> ;;   given that its been interned already
> ;;   otherwise it just returns nil
> ;; |foo| is simply the symbol with name "foo"
> ;;   so (eql (find-symbol "foo") '|foo|) is t
> ;;   the reason for this is that the reader interned |foo|
> ;;   when it read it.
>
> ;; The macro string-case is permitted to emulate this by
> ;; interning a number of strings, One for each key in the case
> keyforms
>
> ;; There are 3 cases to consider for case keyforms
> ;;  * string - Match the string alone
> ;;  * list of strings - Match any single one of the strings
> ;;  * the symbol otherwise - always match
>
> (defmacro string-case (keyform &body cases)
>   `(case (find-symbol ,keyform)

Read CLHS FIND-SYMBOL.  If there is no symbol named by the string in
the current package, it won't intern any, and it will return NIL.

(string-case "No symbol named like this"
  (("NIL") (print 'oops)))

will probably print OOPS.


The other problem is that the interning is done in the current
package, that is, the package refered by CL:*PACKAGE*.


>      ,@(mapcar #'(lambda (case &aux (key (first case))
>                                     (body (rest case)))
>                    `(,(etypecase key
>                         (string (intern key))
>                         (list (mapcar #'intern key))
>                         (symbol (assert (eql key 'otherwise))
>                                 'otherwise))
>                       ,@body))
>                cases)))

and here you are interning the key in the package refered by
CL:*PACKAGE* at macroexpansion time, while the keyform is searched in
the package refered by CL:*PACKAGE* at run-time.

(in-package :utilities)
(defmacro string-case ...)
(in-package :source)
(defun f (x)
  (utility:string-case x (("Try") 'ok) (otherwise 'bad)))
(in-package :client)
(source:f "Try") ; returns SOURCE::BAD


Now, you won't cut it, be it by using FIND-SYMBOL or INTERN, or
explicitely, you have to unify the strings, if you want to test them
with EQL as CASE does it.  

For the above mentionned package problems, I'd advise to avoid
symbols.  You could "intern" these strings in your own hash-table, or
by any other mean.  But you have to be careful if you try to "intern"
them at macroexpansion time, to carry over the data structure to
run-time (in between, there may be a fasl file generated and a LOAD).


-- 
__Pascal Bourguignon__                     http://www.informatimago.com/

NOTE: The most fundamental particles in this product are held
together by a "gluing" force about which little is currently known
and whose adhesive power can therefore not be permanently
guaranteed.