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