unofficial mirror of help-gnu-emacs@gnu.org
 help / color / mirror / Atom feed
From: Pascal Bourguignon <pjb@informatimago.com>
To: help-gnu-emacs@gnu.org
Subject: Re: A macro and an unwanted containing list in the resulting form
Date: Wed, 23 May 2007 14:04:53 +0200	[thread overview]
Message-ID: <87ejl7vj8a.fsf@thalassa.lan.informatimago.com> (raw)
In-Reply-To: mailman.1062.1179919591.32220.help-gnu-emacs@gnu.org

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	other threads:[~2007-05-23 12:04 UTC|newest]

Thread overview: 13+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
     [not found] <mailman.1062.1179919591.32220.help-gnu-emacs@gnu.org>
2007-05-23 12:04 ` Pascal Bourguignon [this message]
2007-05-23 21:32   ` A macro and an unwanted containing list in the resulting form Sebastian Tennant
     [not found]   ` <mailman.1104.1179956594.32220.help-gnu-emacs@gnu.org>
2007-05-23 23:48     ` Pascal Bourguignon
2007-05-23 11:28 Sebastian Tennant
2007-05-23 11:39 ` Juanma Barranquero
2007-05-23 12:51   ` Sebastian Tennant
2007-05-23 13:14     ` Juanma Barranquero
2007-05-23 21:40       ` Sebastian Tennant
     [not found]   ` <mailman.1069.1179924576.32220.help-gnu-emacs@gnu.org>
2007-05-23 16:57     ` Pascal Bourguignon
2007-05-23 21:45       ` Sebastian Tennant
     [not found]       ` <mailman.1113.1179959874.32220.help-gnu-emacs@gnu.org>
2007-05-24  8:47         ` Tim X
2007-05-24 13:49           ` Sebastian Tennant
2007-05-24 16:12       ` Johan Bockgård

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.gnu.org/software/emacs/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=87ejl7vj8a.fsf@thalassa.lan.informatimago.com \
    --to=pjb@informatimago.com \
    --cc=help-gnu-emacs@gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).