[comp.lang.lisp] Question about Macros

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

[Using Lucid Common Lisp on an Apollo Workstation.]

I'm trying to build a macro to create ``rules''.  A rule has some named
components which get turned into lambdas and then later apply-ed by an
interpreter.

So, for example:

	(rule:define  a-rule-name
		(test (eql *spec* 'hyper))
		(action (cons *spec* 'foo)))

In this example, "*spec*" is a parameter that will be passed in to each of
the various parts when executed.  For example, test gets turned into:

	(lambda (*spec*) (eql *spec* 'hyper))

To do this, I write a simple defmacro, the important part of which looks
like this:

	`(setf (structure-test foo)
	       #'(lambda (*spec*) ,@test-part))

Let's say that I define this macro in the RULE package.  Later on, I go to 
use this in the USER package, and looking at the macro-expansion I see 
this:

	(lambda (rule::*spec*) (eql *spec* 'hyper))

Not what I expected.  What's happened?  Well, during the reading of the list 
that forms the backquote structure in my macro, the reader found "*spec*" and
turned that into an atom in the RULE package, hence rule::*spec*.

What's the solution?  I have to delay the de-reference of the symbol until 
the macro is actually expanded.  I came up with this:

	`(setf (structure-test foo)
		#'(lambda (,(intern "*SPEC*")) ,@test-part))

Which (surprise!) works, but which I find pretty ugly and disgusting.

So, is there a better way to do this?  Or is my whole approach wrong-minded?

 
    Scott R. Turner
    UCLA Computer Science     "If you act like a dumbshit, they'll treat you like an equal."
    Domain: srt@cs.ucla.edu

barmar@think.com (Barry Margolin) (02/03/90)

In article <31480@shemp.CS.UCLA.EDU> srt@maui.cs.ucla.edu (Scott Turner) writes:
>What's the solution?  I have to delay the de-reference of the symbol until 
>the macro is actually expanded.  I came up with this:
>	`(setf (structure-test foo)
>		#'(lambda (,(intern "*SPEC*")) ,@test-part))
>Which (surprise!) works, but which I find pretty ugly and disgusting.
>So, is there a better way to do this?  Or is my whole approach wrong-minded?

I have a couple of ideas.

1. Require the users of your package to (import 'rule::*spec*).

2. Don't automatically bind the variable *spec*.  Allow the macro user to
specify his variable.  I think this is a good idea regardless of the
package problem.  If you need the special variable bound in order to
communicate among internal routines of the Rule system, bind it using LET
and then pass the value to the functions created by the macro.  The only
problem with this is that if the user writes (setq <his-spec-variable>
<new-value>) it won't change *spec*.

Symbolics Genera has some facilities that work like your macro; for
instance, their ADVISE macro uses the variables ARGLIST and VALUES
similarly.  Their solution is a variant on #1 above -- these symbols are
external in the COMMON-LISP package (VALUES is a CL function name, and
ARGLIST is a CL declaration name), and most user programs are in packages
that inherit from COMMON-LISP, so they get the right symbol.  However, I
wouldn't suggest this solution for your problem.
--
Barry Margolin, Thinking Machines Corp.

barmar@think.com
{uunet,harvard}!think!barmar

eliot@phoenix.Princeton.EDU (Eliot Handelman) (02/03/90)

In article <31480@shemp.CS.UCLA.EDU> srt@maui.cs.ucla.edu (Scott Turner) writes:
;
;	`(setf (structure-test foo)
;	       #'(lambda (*spec*) ,@test-part))
;
;Let's say that I define this macro in the RULE package.  Later on, I go to 
;use this in the USER package, and looking at the macro-expansion I see 
;this:
;
;	(lambda (rule::*spec*) (eql *spec* 'hyper))

Well, if *spec* is special, as I maybe was your intention from using the
stars, then you don't need the lambda list at all, so that's one solution.

A second is to let the rule specify its parameters, something like this:

(defmacro defrule (name params . test-part)
  `(setf (structure-test ',name)
	 #'(lambda ,params ,@test-part)))

Then you say:

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

It might eventually be handy to generalize in this way, supposing you decide
that the rules are interested in more than one parameter.

Humbly offered,

--Eliot Handelman
  Princeton U., Music

lou@atanasoff.rutgers.edu (Lou Steinberg) (02/06/90)

In article <31480@shemp.CS.UCLA.EDU> srt@maui.cs.ucla.edu (Scott Turner) writes:

> [...] I write a simple defmacro, the important part of which looks
> like this:
> 
> 	`(setf (structure-test foo)
> 	       #'(lambda (*spec*) ,@test-part))
> 
> Let's say that I define this macro in the RULE package.  Later on, I go to 
> use this in the USER package, and looking at the macro-expansion I see 
> this:
> 
> 	(lambda (rule::*spec*) (eql *spec* 'hyper))
> 
> What's the solution?  I have to delay the de-reference of the symbol until 
> the macro is actually expanded.  I came up with this:
> 
> 	`(setf (structure-test foo)
> 		#'(lambda (,(intern "*SPEC*")) ,@test-part))

Even this is not guaranteed to work - it depends on the current
package being the same at macro expand time as it was when the macro
call was read.  On some systems, the interpreter expands macros at
macro call time, when the package may be different.  To really do it
right, you need to search the value of test-part for any symbol whose
print-name is "*SPEC*", and use that symbol for your lambda variable.
Of course, this means different rules may be using different lambda
variables, if they were read in under different packages.

A possibly cleaner solution is to find any symbol in test-part that
has a print-name of *SPEC* and replace it with YOUR symbol *SPEC*.

	 `(setf (structure-test foo)
 		#'(lambda (*SPEC*)
		   ,@(subst-if  '*SPEC* 
		             #'(lambda (x) (and (symbol x)
					        (string-equal (symbol-name x)
					                      "*SPEC*")))n
			     test-part)))

(Warning - the above is not debugged.)
-- 
					Lou Steinberg

uucp:   {pretty much any major site}!rutgers!aramis.rutgers.edu!lou 
arpa:   lou@cs.rutgers.edu

dan@meritaus.UUCP (Daniel Haug) (02/06/90)

From article <31480@shemp.CS.UCLA.EDU>, by srt@maui.cs.ucla.edu (Scott Turner):
> Let's say that I define this macro in the RULE package.  Later on, I go to 
> use this in the USER package, and looking at the macro-expansion I see 
> this:
> 
> 	(lambda (rule::*spec*) (eql *spec* 'hyper))
> 
> Not what I expected.  What's happened?  Well, during the reading of the list 
> that forms the backquote structure in my macro, the reader found "*spec*" and
> turned that into an atom in the RULE package, hence rule::*spec*.
> 

If I were writing this, and wanted to have some lexical variables available
to the user of RULE, I would export these symbols from RULE.  Then the
user of the RULE system would either have to USE the RULE package, or
explicitly reference the variables with a RULE: prefix:

So, your rule system would define this:

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

and the user would do the following from his/her own package (a.k.a USER):

  (use-package :rule)
  (define some-rule ()
    (eql *SPEC* 'hyper)
    ...)

Also humbly offered...
-- 
dan haug
====================================================================
Phonenet: (512)338-2450
Internet: execu!sequoia!meritaus!dan@cs.utexas.edu
    UUCP: {uunet, cs.utexas.edu!execu, texbell}!sequoia!meritaus!dan

``When all you have is a hammer, everything begins to look like a nail.''

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

In article <Feb.5.11.39.45.1990.3151@atanasoff.rutgers.edu> lou@atanasoff.rutgers.edu (Lou Steinberg) writes:
>
> [...] To really do it right, you need to search the value of test-part for
> any symbol whose print-name is "*SPEC*", and use that symbol for your lambda
> variable.  [...]
> 
> A possibly cleaner solution is to find any symbol in test-part that
> has a print-name of *SPEC* and replace it with YOUR symbol *SPEC*.
> 
> 	 `(setf (structure-test foo)
>  		#'(lambda (*SPEC*)
> 		   ,@(subst-if  '*SPEC* 
> 		             #'(lambda (x) (and (symbol x)
> 					        (string-equal (symbol-name x)
> 					                      "*SPEC*")))n
> 			     test-part)))
> 

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."

Don't try to circumvent the namespace hierarchy like this; you *will* lose, 
eventually.  Come at it from the other direction - instead of bashing the code
so that the symbols are the same, manipulate the hierarchy of your packages so
that the symbols come out equivalent automatically.

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.  It will make your life much
happier.

		-- Jamie

lou@atanasoff.rutgers.edu (Lou Steinberg) (02/06/90)

In article <21779@pasteur.Berkeley.EDU> jwz@teak.berkeley.edu (Jamie Zawinski) writes:

> In article <Feb.5.11.39.45.1990.3151@atanasoff.rutgers.edu> lou@atanasoff.rutgers.edu (Lou Steinberg) writes:
> >
> > A possibly cleaner solution is [...]
                    ^^
> 
> 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."

Please note the indicated 2 characters above.

> 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.  It will make your life much
> happier.

You are right, but I suspect that Mr. Turner *thought* he understood
the package system when he started, as you and I no doubt both *think*
we understand it even though (due to the complexity) there may well be
gotcha's lurking for us as well.
-- 
					Lou Steinberg

uucp:   {pretty much any major site}!rutgers!aramis.rutgers.edu!lou 
arpa:   lou@cs.rutgers.edu