From: Stefan Nobis
Subject: macro to create functions
Date: 
Message-ID: <87hdjdyqxf.fsf@snobis.de>
Hi.

I want to write a macro that does some bookkeeping and then
creates a function like defun. Here's my try:

(defmacro def-fun ((fn-name key) &body body)
  (when (not (boundp '*commands*))
    (defparameter *commands* nil))
  (push (cons key fn-name) *commands*)
  `(defun ,fn-name (id args)
    ,@body))

to be used like this:

(def-fun (foo "test")
   (princ "Here's foo."))

Is the above macro the right way to do this? At least it seems to
work (with clisp).

-- 
Stefan.

From: M Jared Finder
Subject: Re: macro to create functions
Date: 
Message-ID: <42370c24_3@x-privat.org>
Stefan Nobis wrote:
> Hi.
> 
> I want to write a macro that does some bookkeeping and then
> creates a function like defun. Here's my try:
> 
> (defmacro def-fun ((fn-name key) &body body)
>   (when (not (boundp '*commands*))
>     (defparameter *commands* nil))
>   (push (cons key fn-name) *commands*)
>   `(defun ,fn-name (id args)
>     ,@body))
> 
> to be used like this:
> 
> (def-fun (foo "test")
>    (princ "Here's foo."))
> 
> Is the above macro the right way to do this? At least it seems to
> work (with clisp).

While the macro will work (in that it does something), it is probably 
not doing what you want.  My guess is that you want something more like 
this:

(defmacro def-fun ((fn-name key) &body body)
   `(progn
      (defvar *commands* nil)
      (push (cons ,key ,fn-name) *commands*)
      (defun ,fn-name (id args)
        ,@body)))

The primary differenece is that *commands* will get set at load time 
instead of at macro-expansion time.  I also replaced DEFPARAMETER with 
DEFVAR.  DEFVAR is like DEFPARAMETER except it does not modify the 
variable if it is already bound.

I also have two stylistic questions.  Are you certain that all functions 
you want to define with DEF-FUN will want two parameters, and those will 
always be named ID and ARGS?  If not, you'll probably want to make the 
lambda-list a parameter to the macro.  Even if you are, it might be a 
good idea anyway, to have the variables sitting there, like in a DEFUN.

The oddest thing is the mapping you are creating between symbols and 
strings.  I'd imagine that normally the function name and key would be 
the same string, so you could use the symbol itself as the key and 
SYMBOL-NAME and SYMBOL-FUNCTION to get to the string representation and 
the function itself.

   -- MJF
From: Peter Seibel
Subject: Re: macro to create functions
Date: 
Message-ID: <m37jk87ryi.fsf@gigamonkeys.com>
M Jared Finder <·····@hpalace.com> writes:

> Stefan Nobis wrote:
>> Hi.
>> I want to write a macro that does some bookkeeping and then
>> creates a function like defun. Here's my try:
>> (defmacro def-fun ((fn-name key) &body body)
>>   (when (not (boundp '*commands*))
>>     (defparameter *commands* nil))
>>   (push (cons key fn-name) *commands*)
>>   `(defun ,fn-name (id args)
>>     ,@body))
>> to be used like this:
>> (def-fun (foo "test")
>>    (princ "Here's foo."))
>> Is the above macro the right way to do this? At least it seems to
>> work (with clisp).
>
> While the macro will work (in that it does something), it is
> probably not doing what you want. My guess is that you want
> something more like this:
>
> (defmacro def-fun ((fn-name key) &body body)
>    `(progn
>       (defvar *commands* nil)
>       (push (cons ,key ,fn-name) *commands*)
>       (defun ,fn-name (id args)
>         ,@body)))
>

You may also want to wrap the DEFVAR and PUSH in EVAL-WHEN like this:

  (defmacro def-fun ((fn-name key) &body body)
     `(progn
        (defun ,fn-name (id args)
          ,@body)
        (eval-when (:compile-toplevel :load-toplevel :execute)
          (defvar *commands* nil)
          (push (cons ,key ,fn-name) *commands*))))

You only need to do that if other code that is going to run at compile
time (e.g. other macro expanders) expect the data in *commands* to be
available. (I reordered the DEFUN and the bookkeeping per Wade's
suggestion.)

-Peter

-- 
Peter Seibel                                     ·····@gigamonkeys.com

         Lisp is the red pill. -- John Fraser, comp.lang.lisp
From: Wade Humeniuk
Subject: Re: macro to create functions
Date: 
Message-ID: <nMDZd.57929$fc4.20598@edtnps89>
Stefan Nobis wrote:
> Hi.
> 
> I want to write a macro that does some bookkeeping and then
> creates a function like defun. Here's my try:
> 
> (defmacro def-fun ((fn-name key) &body body)
>   (when (not (boundp '*commands*))
>     (defparameter *commands* nil))
>   (push (cons key fn-name) *commands*)
>   `(defun ,fn-name (id args)
>     ,@body))
> 
> to be used like this:
> 
> (def-fun (foo "test")
>    (princ "Here's foo."))
> 


Well there are a few problems with the above.

1) Use defvar instead of defparameter
2) Add the def-fun to *commands* after a _successful_ defun.  If
there is a compilation/evaluation failure you do not want the
function to be put into *commands*
3) I am not sure about the use of key in your def-fun.  What is its purpose?
It seems redundant.
4) hard-coding the (id args) args for the function is a so-so idea.  I would
allow them to be specified in def-fun.

All in all here would be my version.  I would put the commands into
a hash-table, its easier and it allows redefinition of a command
function.

(defvar *commands* (make-hash-table :test 'equal))

(defmacro def-fun ((fn-name key) args &body body)
   `(progn
      (defun ,fn-name ,args
        ,@body)
      (setf (gethash ,key *commands*) ',fn-name)))

CL-USER 13 > (def-fun (foo "test") (id args)
               "Here's foo")
FOO

CL-USER 14 > (gethash "test" *commands*)
FOO
T

CL-USER 15 > (foo 1 2)
"Here's foo"

CL-USER 16 >

Wade
From: Stefan Nobis
Subject: Re: macro to create functions
Date: 
Message-ID: <87wts7dhpi.fsf@snobis.de>
Wade Humeniuk <··················@telus.net> writes:

> 1) Use defvar instead of defparameter

I tried a global defvar, but then i got an error (IIRC about
*commands* is empty). That's my try:

(defvar *commands* nil)
(defmacro def-fun ((fn-name key) &body body)
  (push (cons key fn-name) *commands)
  `(...))

Why doesn't this work?

> 2) Add the def-fun to *commands* after a _successful_ defun.

Oh, yes. Really good idea! Thank you.

> 3) I am not sure about the use of key in your def-fun.  What is
>    its purpose?

I'm trying to write a protocol parser. I don't want the command
names of the protocol to be the names of the functions, so i need
some kind of mapping. Alists are used because there are not very
many commands (about 20-30, at most about 100) and i think i
sometimes may need to be able to override commands temporarily,
which seems quite easy with alists (and a bit more complicated
with hashes).

> 4) hard-coding the (id args) args for the function is a so-so
>    idea. I would allow them to be specified in def-fun.

First point: I didn't know how to do that.

But even if i knew, i think, it's a good solution this way. The
parser collects the id of the command line, the command name and
every possible arguments in a list. I want each command to process
the argument list. So args is a list of numbers and strings to be
processed. There may be invalid parameters, too few or too many
and the parse-function should not care about all these details.

By the way: If i have a simple loop like

  (loop (print (eval (read))))

is there a way for a function like eval to abort this loop (if it
encounters a command like "quit")?

> (defmacro def-fun ((fn-name key) args &body body)
>    `(progn
>       (defun ,fn-name ,args
>         ,@body)
>       (setf (gethash ,key *commands*) ',fn-name)))

Looks very good. Thank you very much for your ideas.

-- 
Stefan.
From: ivant
Subject: Re: macro to create functions
Date: 
Message-ID: <1110977950.508740.283210@o13g2000cwo.googlegroups.com>
Stefan Nobis wrote:
> Wade Humeniuk <··················@telus.net> writes:
>
> > 1) Use defvar instead of defparameter
>
> I tried a global defvar, but then i got an error (IIRC about
> *commands* is empty). That's my try:
>
> (defvar *commands* nil)
> (defmacro def-fun ((fn-name key) &body body)
>   (push (cons key fn-name) *commands)
>   `(...))
>
> Why doesn't this work?

Probably because you've forgot the second asterisk?

Also, it's not a good idea to have side-effects in macro *expanders*,
because a macro can be expanded multiple times even for one use.  Note
that it's ok to have side effects in the expanded code, that is in the
result of macroexpand.

--
All languages are equal,
but Lisp is more equal than others.
From: Thomas A. Russ
Subject: Re: macro to create functions
Date: 
Message-ID: <ymiacp3yvvs.fsf@sevak.isi.edu>
"ivant" <········@gmail.com> writes:

> 
> Stefan Nobis wrote:
> > Wade Humeniuk <··················@telus.net> writes:
> >
> > > 1) Use defvar instead of defparameter
> >
> > I tried a global defvar, but then i got an error (IIRC about
> > *commands* is empty). That's my try:
> >
> > (defvar *commands* nil)
> > (defmacro def-fun ((fn-name key) &body body)
> >   (push (cons key fn-name) *commands)
> >   `(...))
> >
> > Why doesn't this work?
> 
> Probably because you've forgot the second asterisk?
> 
> Also, it's not a good idea to have side-effects in macro *expanders*,
> because a macro can be expanded multiple times even for one use.  Note
> that it's ok to have side effects in the expanded code, that is in the
> result of macroexpand.

And even worse, the expansion would happen at compile-time.  So if you
used this function in a file which is then compiled, all of the bindings
in *commands* would be in the compile time environment.  When you loaded
the compiled file into a fresh lisp image, none of those bindings would
be present.  That is why you want the registration to be part of the
expansion of the macro.

If for some reason you need to have the *commands* table available
during the compilation process, then you need to learn about the
EVAL-WHEN construct.  But I don't think that applies here.


-- 
Thomas A. Russ,  USC/Information Sciences Institute
From: Barry Margolin
Subject: Re: macro to create functions
Date: 
Message-ID: <barmar-8E09A6.20195116032005@comcast.dca.giganews.com>
In article <························@o13g2000cwo.googlegroups.com>,
 "ivant" <········@gmail.com> wrote:

> Stefan Nobis wrote:
> > Wade Humeniuk <··················@telus.net> writes:
> >
> > > 1) Use defvar instead of defparameter
> >
> > I tried a global defvar, but then i got an error (IIRC about
> > *commands* is empty). That's my try:
> >
> > (defvar *commands* nil)
> > (defmacro def-fun ((fn-name key) &body body)
> >   (push (cons key fn-name) *commands)
> >   `(...))
> >
> > Why doesn't this work?
> 
> Probably because you've forgot the second asterisk?
> 
> Also, it's not a good idea to have side-effects in macro *expanders*,
> because a macro can be expanded multiple times even for one use.  Note
> that it's ok to have side effects in the expanded code, that is in the
> result of macroexpand.

Also, the macro's side effects won't persist across Lisp invocations.  
If you compile a file containing DEF-FUNs, and later load the fasl into 
another Lisp session, they won't be in *COMMANDS* any more.

-- 
Barry Margolin, ······@alum.mit.edu
Arlington, MA
*** PLEASE post questions in newsgroups, not directly to me ***
From: Pascal Bourguignon
Subject: Re: macro to create functions
Date: 
Message-ID: <877jk7rbwp.fsf@thalassa.informatimago.com>
Stefan Nobis <······@gmx.de> writes:

> Wade Humeniuk <··················@telus.net> writes:
> 
> > 1) Use defvar instead of defparameter
> 
> I tried a global defvar, but then i got an error (IIRC about
> *commands* is empty). That's my try:
> 
> (defvar *commands* nil)
> (defmacro def-fun ((fn-name key) &body body)
>   (push (cons key fn-name) *commands)
>   `(...))
> 
> Why doesn't this work?

Because the effects of defvar will be seen only after the compilation,
and it seems in your case macro-expansion time is during the
compilation.  Use the following to get a portable program:

(eval-when (:compile-toplevel :load-toplevel :execute)
    (defvar *commands* nil))
 

> By the way: If i have a simple loop like
> 
>   (loop (print (eval (read))))
> 
> is there a way for a function like eval to abort this loop (if it
> encounters a command like "quit")?

There are several ways:

    extended loo/loop-finish
    tagbody/go
    catch/throw
    block/return-from 



-- 
__Pascal Bourguignon__                     http://www.informatimago.com/
Small brave carnivores
Kill pine cones and mosquitoes
Fear vacuum cleaner