help-gnu-emacs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: A macro and an unwanted containing list in the resulting form


From: Pascal Bourguignon
Subject: Re: A macro and an unwanted containing list in the resulting form
Date: Wed, 23 May 2007 14:04:53 +0200
User-agent: Gnus/5.1008 (Gnus v5.10.8) Emacs/22.0.99 (gnu/linux)

Sebastian Tennant <sebyte@smolny.plus.com> writes:
> Trying to write a little macro to automate building a (cond ...)
> expression from an alist, where the alist is of the form:
>
>    (("string" . FORM) ...)
>
> and FORM is evaluated if the string passes an equality test.
>
> Here's where I've got to so far.  As you can see, the cond clauses are
> contained within an unwanted list:
>
>
>   (defmacro build-cond (alist)
>     (list 'cond
>           (mapcar '(lambda (each)
>                      (cons (list 'equal 'my-var (car each)) (list (cdr 
> each))))
>                   alist)))
>
>   (macroexpand '(build-cond (("hello" . (message "hi"))
>                              ("goodbye" . (message "bye")))
>                             ))
>
>   (cond (((equal e "hello") (message "hi"))
>         ^((equal e "goodbye") (message "bye"))))
>         |                                     ^
>         |                                     |
>         +--------- unwanted list -------------+
>
> Clearly my approach is wrong (because this is how mapcar behaves), so
> what is the best way to go about this?

It's not bad. Instead of building a new list around the list returned
by mapcar:

         (((equal e "hello") (message "hi"))
          ((equal e "goodbye") (message "bye"))) 

you could just prepend this list with the symbol cond:

   (cons 'cond 
          (mapcar '(lambda (each)
                      (cons (list 'equal 'my-var (car each)) (list (cdr each))))
                   alist))

Now, there are operators that are very useful to build expressions
building expressions:

    backquote   `
    unquote     ,
    unsplice    ,@


Instead of writting:

    (let ((x 'hi) (y 'ho))

       (list 'a 'b 'c x 'd y 'e)   )  ; --> (a b c hi d ho e)

you can write:

    (let ((x 'hi) (y 'ho))

       `(a b c ,x d ,y e)   )         ; --> (a b c hi d ho e)


Now, with a list:

    (let ((x '(1 2 3)))

        `(a b ,x c d)    )            ; --> (a b (1 2 3) c d)

When you want the elements in the list bound to x to appear at the
same level as x, you can use unsplice:

    (let ((x '(1 2 3)))

        `(a b ,@x c d)    )           ; --> (a b 1 2 3 c d)


So when you want to write s-exps, you can easily see what the result
will be:

  (defmacro build-cond (alist)
    `(cond ,@(mapcar (lambda (each)
                        `((equal my-var ,(car each)) ,(cdr each)))
                     alist)))

And never put a quote before a lambda expression!!!  It works by
chance in emacs lisp, but it prevents the compiler to compile the
anonymous function, and it wouldn't work in other lisps.


Also, it would be better if you hadn't magic variable names in your
macro. Instead of using my-var, let the user of the macro decide what
variable name should be tested.  You could also  use a better name
than build-cond; for example, string-case.  And let's improve a little
the syntax, removing one layer of useless parentheses, getting the
list of clauses from the &rest of the arguments:

  (defmacro string-case (var-name &rest alist)
    `(cond ,@(mapcar (lambda (each)
                        `((equal ,var-name ,(car each)) ,(cdr each)))
                     alist)))

So now you can write:

   (string-case e
      ("hello"   . (message "hi"))
      ("goodbye" . (message "bye")))

But if you write:

   (string-case (concat "hel" "lo")
      ("hello"   . (message "hi"))
      ("goodbye" . (message "bye")))

the expansion will evaluate several times (concat "hel" "lo"), which
would be very bad when the expression has side effects.  We need to
take care of that in the macro, building a temporary and unique
variable name with gensym, and binding it to the expression:

  (defmacro string-case (strexpr &rest alist)
    (let ((var-name (gensym)))
       `(let ((,var-name ,strexpr))
          (cond ,@(mapcar (lambda (each)
                         `((equal ,var-name ,(car each)) ,(cdr each)))
                     alist)))))


And finally, it would be better if we could have several forms in each
branches, and if we lost the dot:

  (defmacro string-case (strexpr &rest alist)
    (let ((var-name (gensym)))
       `(let ((,var-name ,strexpr))
          (cond ,@(mapcar (lambda (each)
                         `((equal ,var-name ,(car each)) ,@(cdr each)))
                     alist)))))


(macroexpand '(string-case (aref my-strings (incf i))
                 ("hello"    (message "hi") (message "ho"))
                 ("goodbye"  (message "bye"))))
-->
(let ((G42298 (aref my-strings (incf i))))
  (cond
    ((equal G42298 "hello")
     (message "hi")
     (message "ho"))
    ((equal G42298 "goodbye")
     (message "bye"))))


 
__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.


reply via email to

[Prev in Thread] Current Thread [Next in Thread]