From: Glen Able
Subject: My first macro
Date: 
Message-ID: <d4aclk$sjk$1$8300dec7@news.demon.co.uk>
Hurrah!  It's not much use, but my brain seems pleased that I did it.

(defmacro switch (var &rest expr)
   (append '(cond)
   (loop for item in expr collecting
         (cond ((eql (car item)
                     'case)
                `((eql ,var ,(cadr item))
                  ,(caddr item)))
               (t
                `(t ,(cadr item)))))))


So I can write:

(defun test (val)
   (switch val
           (case 3
             (format t "3"))
           (case 5
             (format t "5"))
           (default
            (format t "default"))))


So, I have some questions...

1) Where I compare with 'case, what's happening there?  Am I recklessly 
creating a symbol called "case", or what?  Is this the same as what the 
loop macro does for "collecting" etc?

2) Should I be checking for errors, e.g. validating that 'item' is a 
list etc.  If so, how to signal errors?

3) Before I happened upon "loop", I was struggling to manually add 
individual parentheses etc. to a list...is this ever necessary when 
creating macros (or even possible)?

Any feedback always appreciated,
thanks guys!

P.S. For people in the uk, www.bookfellas.co.uk have PCL (in the AI - 
LISP section :) - I ordered yesterday and it was apparently dispatched 
within a couple of hours.

From: alex goldman
Subject: Re: My first macro
Date: 
Message-ID: <53083425.hrQQYHKBt0@yahoo.com>
Glen Able wrote:

> So, I have some questions...

FYI, there are CASE, ECASE and CCASE in CL, that do the same job, but with
less syntax.

> 1) Where I compare with 'case, what's happening there?  Am I recklessly
> creating a symbol called "case", or what?  

It's created once at read-time. This doesn't affect your runtime speed.

> Is this the same as what the 
> loop macro does for "collecting" etc?

Until you know about packages, it's good enough. But when you decide to keep
the macro definition in one package, and use it in another, you'll have to
replace the EQL test with (equal (symbol-name (car item)) "CASE")

> 2) Should I be checking for errors, e.g. validating that 'item' is a
> list etc.  If so, how to signal errors?

It's a question of taste, discipline, time and motivation.

> 3) Before I happened upon "loop", I was struggling to manually add
> individual parentheses etc. to a list...is this ever necessary when
> creating macros (or even possible)?

You can rewrite the macro using MAPCAR instead of LOOP. You'll always need
some looping structure though, since you don't know the number of `cases'
before the macro is used.
From: Frank Buss
Subject: Re: My first macro
Date: 
Message-ID: <d4ahvp$n9s$1@newsreader3.netcologne.de>
Glen Able <·········@gmail.com> wrote:

> So I can write:
> 
> (defun test (val)
>    (switch val
>            (case 3
>              (format t "3"))
>            (case 5
>              (format t "5"))
>            (default
>             (format t "default"))))

in Lisp you would it like this:

(defun test (val)
  (format t (case val
              (3 "3")
              (5 "5")
              (otherwise "default"))))

> 2) Should I be checking for errors, e.g. validating that 'item' is a 
> list etc.  If so, how to signal errors?

you can use #'error for signalling errors.

> 3) Before I happened upon "loop", I was struggling to manually add 
> individual parentheses etc. to a list...is this ever necessary when 
> creating macros (or even possible)?

you don't build a list by concatenating the string representation, so it is 
not necessary to add parantheses, but you need to cons and append or better 
optimized with temporary variables and replacd, like the macroexpansion of 
loop in LispWorks shows (try (macroexpand '(loop for i in '(1 2 3) collect 
i))).

-- 
Frank Bu�, ··@frank-buss.de
http://www.frank-buss.de, http://www.it4-systems.de
From: Rainer Joswig
Subject: Re: My first macro
Date: 
Message-ID: <joswig-7E4C22.12492022042005@news-50.ams.giganews.com>
In article <············@newsreader3.netcologne.de>,
 Frank Buss <··@frank-buss.de> wrote:

> Glen Able <·········@gmail.com> wrote:
> 
> > So I can write:
> > 
> > (defun test (val)
> >    (switch val
> >            (case 3
> >              (format t "3"))
> >            (case 5
> >              (format t "5"))
> >            (default
> >             (format t "default"))))
> 
> in Lisp you would it like this:
> 
> (defun test (val)
>   (format t (case val
>               (3 "3")
>               (5 "5")
>               (otherwise "default"))))

Actually I wouldn't use FORMAT at all.

> 
> > 2) Should I be checking for errors, e.g. validating that 'item' is a 
> > list etc.  If so, how to signal errors?
> 
> you can use #'error for signalling errors.
> 
> > 3) Before I happened upon "loop", I was struggling to manually add 
> > individual parentheses etc. to a list...is this ever necessary when 
> > creating macros (or even possible)?
> 
> you don't build a list by concatenating the string representation, so it is 
> not necessary to add parantheses, but you need to cons and append or better 
> optimized with temporary variables and replacd, like the macroexpansion of 
> loop in LispWorks shows (try (macroexpand '(loop for i in '(1 2 3) collect 
> i))).

This is a level of optimization that is not needed while writing
user level code, since LOOP or other constructs will hide it.

It is useful to know both recursive list operations and iterative
loop operations.

Many APPENDs in the source are a sign that it is inefficient.
Maybe even so inefficient that an immediate optimization
is useful.
From: Rainer Joswig
Subject: Re: My first macro
Date: 
Message-ID: <joswig-46B6DB.11510722042005@news-50.ams.giganews.com>
In article <·····················@news.demon.co.uk>,
 Glen Able <·········@gmail.com> wrote:

> Hurrah!  It's not much use, but my brain seems pleased that I did it.
> 
> (defmacro switch (var &rest expr)
>    (append '(cond)
>    (loop for item in expr collecting
>          (cond ((eql (car item)
>                      'case)
>                 `((eql ,var ,(cadr item))
>                   ,(caddr item)))
>                (t
>                 `(t ,(cadr item)))))))
> 
> 
> So I can write:
> 
> (defun test (val)
>    (switch val
>            (case 3
>              (format t "3"))
>            (case 5
>              (format t "5"))
>            (default
>             (format t "default"))))
> 
> 
> So, I have some questions...
> 
> 1) Where I compare with 'case, what's happening there?  Am I recklessly 
> creating a symbol called "case", or what?  Is this the same as what the 
> loop macro does for "collecting" etc?
> 
> 2) Should I be checking for errors, e.g. validating that 'item' is a 
> list etc.  If so, how to signal errors?
> 
> 3) Before I happened upon "loop", I was struggling to manually add 
> individual parentheses etc. to a list...is this ever necessary when 
> creating macros (or even possible)?
> 
> Any feedback always appreciated,
> thanks guys!
> 
> P.S. For people in the uk, www.bookfellas.co.uk have PCL (in the AI - 
> LISP section :) - I ordered yesterday and it was apparently dispatched 
> within a couple of hours.

How about this one:

- no more append
- loop destructuring used to get rid of some CAR/CDR
- ECASE used to dispatch on the clause type, instead of COND
- ECASE generates an error of a clause type is not defined
- ,@ used to splice in lists

(defmacro switch (var &rest expr)
  `(cond
    ,@(loop for (clause-type . rest) in expr collecting
           (ecase clause-type
             (case `((eql ,var ,(car rest))
                     ,@(cdr rest)))
             (default `(t ,@rest))))))
From: Lars Brinkhoff
Subject: Re: My first macro
Date: 
Message-ID: <85vf6fujvj.fsf@junk.nocrew.org>
Rainer Joswig wrote:
> Glen Able wrote:
> > (defmacro switch (var &rest expr)
> >    (append '(cond)
> >    (loop for item in expr collecting
> >          (cond ((eql (car item)
> >                      'case)
> >                 `((eql ,var ,(cadr item))
> >                   ,(caddr item)))
> >                (t
> >                 `(t ,(cadr item)))))))
>
> (defmacro switch (var &rest expr)
>   `(cond
>     ,@(loop for (clause-type . rest) in expr collecting
>            (ecase clause-type
>              (case `((eql ,var ,(car rest))
>                      ,@(cdr rest)))
>              (default `(t ,@rest))))))

I would recommend ensuring that var is only evaluated once:

  (defmacro switch (var &rest expr)
    (let ((val (gensym)))
      `(let ((,val ,var))
         (cond
          ,@(loop for (clause-type . rest) in expr collecting
                 (ecase clause-type
                   (case `((eql ,val ,(car rest))
                           ,@(cdr rest)))
                   (default `(t ,@rest))))))))

For macro-writing newbies: try googling "with-gensyms" and "rebinding"
(or "once-only").
From: Rainer Joswig
Subject: Re: My first macro
Date: 
Message-ID: <joswig-78876D.14030922042005@news-50.ams.giganews.com>
In article <··············@junk.nocrew.org>,
 Lars Brinkhoff <·········@nocrew.org> wrote:

> Rainer Joswig wrote:
> > Glen Able wrote:
> > > (defmacro switch (var &rest expr)
> > >    (append '(cond)
> > >    (loop for item in expr collecting
> > >          (cond ((eql (car item)
> > >                      'case)
> > >                 `((eql ,var ,(cadr item))
> > >                   ,(caddr item)))
> > >                (t
> > >                 `(t ,(cadr item)))))))
> >
> > (defmacro switch (var &rest expr)
> >   `(cond
> >     ,@(loop for (clause-type . rest) in expr collecting
> >            (ecase clause-type
> >              (case `((eql ,var ,(car rest))
> >                      ,@(cdr rest)))
> >              (default `(t ,@rest))))))
> 
> I would recommend ensuring that var is only evaluated once:
> 
>   (defmacro switch (var &rest expr)
>     (let ((val (gensym)))
>       `(let ((,val ,var))
>          (cond
>           ,@(loop for (clause-type . rest) in expr collecting
>                  (ecase clause-type
>                    (case `((eql ,val ,(car rest))
>                            ,@(cdr rest)))
>                    (default `(t ,@rest))))))))
> 
> For macro-writing newbies: try googling "with-gensyms" and "rebinding"
> (or "once-only").

Good point!

CL-USER 56 > (pprint (macroexpand-1 '(switch (long-running-function)
                                       (case 3
                                         (format t "3"))
                                       (case 5
                                         (format t "5"))
                                       (default
                                        (format t "default")))))

This result is much better:

(LET ((#:G662046 (LONG-RUNNING-FUNCTION)))
  (COND ((EQL #:G662046 3) (FORMAT T "3"))
        ((EQL #:G662046 5) (FORMAT T "5"))
        (T (FORMAT T "default"))))

Than the one before. This will run the function twice. Unnecessarily.

(COND ((EQL (LONG-RUNNING-FUNCTION) 3) (FORMAT T "3"))
      ((EQL (LONG-RUNNING-FUNCTION) 5) (FORMAT T "5"))
      (T (FORMAT T "default")))


One minor thing to add, I should have used &BODY instead of &REST.
This will inform the Lisp system that it can use some
better formatting for source code using the SWITCH macro.


(defmacro switch (var &body expr)
    (let ((val (gensym)))
      `(let ((,val ,var))
         (cond
          ,@(loop for (clause-type . rest) in expr collecting
                 (ecase clause-type
                   (case `((eql ,val ,(car rest))
                           ,@(cdr rest)))
                   (default `(t ,@rest))))))))
From: Kalle Olavi Niemitalo
Subject: Re: My first macro
Date: 
Message-ID: <87hdhx519u.fsf@Astalo.kon.iki.fi>
Lars Brinkhoff <·········@nocrew.org> writes:

> I would recommend ensuring that var is only evaluated once:

If the macro expands to CASE, this occurs automatically.

  (defmacro switch (var &body clauses)
    `(case ,var
       ,@(loop for clause in clauses
            collect (etypecase clause
                      ((cons (eql :case) (cons * list))
                       `((,(cadr clause)) ,@(cddr clause)))
                      ((cons (eql :default) list)
                       `(otherwise ,@(cdr clause)))))))

I changed the parsed CASE and DEFAULT symbols to keywords so that
a package that exports SWITCH need not export them too, and I
used ETYPECASE instead of LOOP destructuring so that things like
(switch (:case)) won't get through.

However, this version generates incorrect code if the (:default ...)
clause is not the last one.  What was the original intention?
From: Glen Able
Subject: Re: My first macro
Date: 
Message-ID: <d4i58e$mma$1$8300dec7@news.demon.co.uk>
Kalle Olavi Niemitalo wrote:
> Lars Brinkhoff <·········@nocrew.org> writes:
> 
> 
>>I would recommend ensuring that var is only evaluated once:

Understood.

> 
> If the macro expands to CASE, this occurs automatically.
> 
>   (defmacro switch (var &body clauses)
>     `(case ,var
>        ,@(loop for clause in clauses
>             collect (etypecase clause
>                       ((cons (eql :case) (cons * list))
>                        `((,(cadr clause)) ,@(cddr clause)))
>                       ((cons (eql :default) list)
>                        `(otherwise ,@(cdr clause)))))))
> 
> I changed the parsed CASE and DEFAULT symbols to keywords so that
> a package that exports SWITCH need not export them too, and I
> used ETYPECASE instead of LOOP destructuring so that things like
> (switch (:case)) won't get through.
> 
> However, this version generates incorrect code if the (:default ...)
> clause is not the last one.  What was the original intention?

That was one of the things I wanted to flag as an error, which I now 
know how to do.

Sincere thanks to everyone for the dozens of ideas that I'm now slowly 
absorbing.  This was just a warm up for a state machine macro that I've 
been thinking about - I've been looking at a few annoyingly repetitive 
bits of code in my C++ day job to see how nicely Lisp could handle them.

I'm finding my dead-tree version of PCL very helpful (well done Peter!) 
and much easier to read than the online version.  www.bookfellas.co.uk 
delivered it within 24 hours of ordering, didn't charge for delivery, 
and had 9% off (total cost �30.93)