[comp.lang.lisp] Summary of Macro Question

srt@maui.cs.ucla.edu (Scott Turner) (02/08/90)

In an earlier article, I posted a question about defining macros in Common
Lisp.  I wanted to define a macro which would allow the user to type in a
form like:

	(rule:define rule-name
		(test (list? *spec*))
		(body (car *spec*)))

Where "*spec*" would be a parameter passed into the lambda wrapped around
test and body, i.e., test would expand into something like:

	(lambda (*spec*) (list? *spec*))

A problem arises in that the expansion actually looks something like this:

	(lambda (rule::*spec*) (list? *spec*))

This arises from the fact that the defmacro is evaluated in one
environment and expanded in another.  I showed the way I hacked around
this (by delaying the evaluation of the parameter using intern) and
asked if there was a better way to deal with this.

One suggestion, made by Barry, Daniel Haug, and Joe Weening was to have
the user import rule::*spec* into the current package.  That's an appealing
solution because it can be (I believe) incorporated into the macro, i.e., 

	(defmacro rule::define (...)
	  `(progn
		(import 'rule::*spec*)
		... ))

This seems to work, and has the advantage of relieving the user of having 
to deal with such things.  (Which of course is the whole point of the macro.)

Similarly, several people suggested making the user type out "rule::*spec*"
but I don't consider that an acceptable interface.

Elliot and Barry also suggested changing the syntax of the macro to allow 
the user to specify his own parameters, i.e., 

	(rule::defrule big-rule (x)
	  (:test   (eql x 'foo))
	  (:action (cons x 'foo)))

As an interface this has some problems.  The rule is to be applied later to 
a fixed set of arguments, and it's confusing to the user to have to specify
something that's going to be fixed anyway.  But it is a solution that might
be useful in other circumstances.

Lou Steinberg pointed out some problems with my suggested solution using 
intern, for which I was grateful.

Finally, Jamie Zawinski had some rather dire advice:

	Don't try to circumvent the namespace hierarchy like this; you
	*will* lose, eventually...

	If you don't feel like you fully understand the package
	system, don't use it.  Put all of your code in one package
	until you do.

Which is probably good advice, though I wonder how one is to learn about the 
package system without trying out things.

Jamie also said:

	I think one of the biggest flaws with the Common Lisp package
	system is that it is confusing.  In fact, it is so confusing,
	that sometimes people get the notion that evil hacks like the
	above are "right" or "clean."

with which I can only agree.  I'm not an inexperienced programmer, in
Lisp or other languages, and I find packages in CL to be confusing and
non-intuitive.  On an abstract intellectual level I both understand
and appreciate the package idea, but on a usage level I constantly
find myself stumbling and mystified.  But I'm a comparative novice at 
CL so perhaps that's to be expected.

Thanks to everyone for the input and reccomendations.
 
    Scott R. Turner
    UCLA Computer Science     "Help Me Please!"
    Domain: srt@cs.ucla.edu

jwz@teak.berkeley.edu (Jamie Zawinski) (02/08/90)

In article <31623@shemp.CS.UCLA.EDU> srt@maui.cs.ucla.edu (Scott Turner) writes:
> 	(defmacro rule::define (...)
> 	  `(progn
> 		(import 'rule::*spec*)
> 		... ))
> 
> This seems to work, and has the advantage of relieving the user of having 
> to deal with such things.  (Which of course is the whole point of the macro.)
> 
> Similarly, several people suggested making the user type out "rule::*spec*"
> but I don't consider that an acceptable interface.

Actually, I doubt this would work if loaded into a fresh environment.  Consider
what happens when you type the form

	(rule:define rule-name
		(test (list? *spec*))
		(body (car *spec*)))

First it is read, producing a list.  In this list are the symbols RULE::DEFINE
and *SPEC* (in the current package), among others.  Next, this list is 
evaluated.  Since the car of the list is a macro, the macroexpansion function
is called to produce another list to evaluate instead.  This would be 
something like

	(progn (import 'rule::*spec*)
	       ...
		 (lambda (*spec*) (list? *spec*))
		...)

Since the call to IMPORT is a part of the macroexpansion output, the 
symbol-names have already been resolved; that happens at read-time.  
Even before the above form is evaluated, there is a symbol called "*SPEC*" in
the current package.  When you call IMPORT to drag the symbol called "*SPEC*"
from the RULE package into the current package, you will get a name-conflict
error.

Rather than trying to change the package hierarchy at run-time, set it up
beforehand.  At the front of the file which defines the rule system itself, 
add these forms:

	(in-package "RULE")
	(export '(rule::define rule::*spec*) "RULE")

and in the package which defines rules, add a form like

	(in-package "RULE-USER" :use '("LISP" "RULE"))

This says that the rule package exports two symbols as its interface to the
outside world: DEFINE and *SPEC*.  Packages which :USE the rule package
will inherit those two symbols as internal; that is, the symbols 
RULE-USER::*SPEC* and RULE::*SPEC* will be the same.  They are not just 
equivalent, they different names for the same symbol.  this will let the 
code's users type 

	(define rule-name
		(test (list? *spec*))
		(body (car *spec*)))

and get the right versions of both *SPEC* and DEFINE.

It is very easy to make the package state inconsistent; since simply typing
a symbol causes it to be created and interned if it does not exist in the
current package, if, before creating the RULE-USER package, you happened to
say something like

	(in-package "RULE-USER")
	'*spec*		; <--- any mention of this symbol

then the form

	(in-package "RULE-USER" :use '("LISP" "RULE"))

will no longer work, because the RULE-USER package is now in an incompatible
state.  This is why it's usually a good idea to set up the package hierarchy 
the way you want it, and then not touch it again.  When you have code that 
alters the package state at run-time (like the define-rule macro quoted at 
the top of this message) you can get into some strange, hard to reproduce,
hard to debug situations.  

>> If you don't feel like you fully understand the package system, don't use
>> it.  Put all of your code in one package until you do.
> 
> Which is probably good advice, though I wonder how one is to learn about the
> package system without trying out things.

You are, of course, absolutely right.  I hope this message was a little less
snide.  :-)

		-- Jamie