views:

84

answers:

4

Hi,

I have a class in Common Lisp:

(defclass my-cool-class()
  ((variable1
    :initarg :variable1
    :accessor variable1
    :initform (error "Must supply value to variable1"))
   (variable2
    :initarg :variable2
    :accessor variable2
    :initform (error "Must supply value to variable2"))

I wanted to create a macro that would simplify this redundancy of typing

(defmacro make-slot (slot-name)
  `(slot-name 
     :initarg :,slot-name
     :accessor :,slot-name
     :initform (error "Must supply value")))

Eventually I'd like to have (defclass my-cool-class () (make-slots '(foo bar baz)) and get foo, bar, and baz out as slots automagically.

But, when I went to do a macroexpand-1 of make-slot, boy howdy did I get reader errors.

The first one was "illegal terminating character after a colon..." and then it kept going.

SBCL 1.0.37.

edit: the examples are syntactically correct on the system, I did some redaction before I copied.

+5  A: 

You can't put macros in code where you want. Read the syntax for a construct in CLHS.

For example you can't do:

(defun foo (make-arg-list 'a 'b) a b)

DEFUN expects an arglist and not a function that creates an arglist.

Lisp expands macros, where Lisp forms are expected. Where other lists (for example a list of slots) are expected, Lisp does not macroexpand.

Similar DEFCLASS expects a list of slots and not a function that creates a list of slots. Similar for the list of slots, DEFCLASS expects each slot to be either a name or a list describing the slot.

See the syntax of DEFCLASS: http://www.lispworks.com/documentation/HyperSpec/Body/m_defcla.htm

You can't also put commas where you want.

Probabaly a basic Lisp book might help. Read about the Lisp syntax.

:,foo

above is not meaningful.

The comma operator puts items into backquoted lists. It does not put items into symbols.

If you want to create a symbol, you need to call INTERN or MAKE-SYMBOL.

Solution

Write a MY-DEFCLASS macro that allows a shorter syntax and expands into DEFCLASS. There are already DEFCLASS* macros that are doing something like that in libraries.

Rainer Joswig
Hm. I thought - mistakenly evidently - that a macro would expand out in a replace fashion; thus, defclass would see the list of slots.
Paul Nathan
@Paul Nathan, how should Lisp decide that MAKE-SLOT is a macro and not the name of a slot? The DEFCLASS syntax is clear, it expects a list of slots. It does not expect a function that evaluates into a list of slots.
Rainer Joswig
@Rainer: Ah, I guess my primary error here is my thinking on how macros are expanded and when.
Paul Nathan
I think the key is that `defclass` will be expanded before `make-slot` (because `defclass` is encountered first). So the data passed to `defclass` will include the unexpanded `make-slot` form. But `defclass` isn't expecting a form there, it's expecting a slot definition.
Nathan Davis
@Nathan Davis. Right, the top-level form is a DEFCLASS form that gets expanded. DEFCLASS is a macro. Such a macro could itself expand forms, but it this case it does not. It returns a new form with several subforms. Just do a macroexpansion for above form (macroexpand '(defclass ...)) and see the result. DEFCLASS does not expand the slot form, it expects real list describing the slots. Evaluation is inside->out. But macroexpansion begins at the outside. Also the generated form may have places that need expansion, but in this case this is also not the case. The slots are already processed.
Rainer Joswig
+1  A: 

As Rainer says, macros are only expanded where a function call would be acceptable.

What I've done to limit the boiler-plate I need to actually type when defining slots, is to have two editor macros, one for slots with a reader and one for slots with an accessor (I seldom have slots with separate writers, but if I did, I'd have to write it by hand).

Vatine
+1  A: 

I normally use something like this

(defmacro mydefclass (name fields)
  `(defclass ,name ()
     ,(let ((res nil))
        (dolist (f fields)
          (let* ((fname (symbol-name f))
                 (kw (intern fname :keyword)))
            (push `(,f :accessor ,kw
                       :initarg ,kw
                       :initform (error
                                  ,(format NIL "Must supply value to ~a"
                                           fname)))
                  res)))
        (nreverse res))))

and then

(mydefclass foo (x y z))

Adding some logic to handle the need for custom slots is very easy (for example you could copy the input verbatim in the expansion when a field is a list and not a symbol)

6502
A: 

Macros are expanded recursively from the top down. In your example, the defclass macro is expanded first -- before your make-slot macro. The code that expands defclass isn't expecting an unexpanded make-slot macro -- it's expecting a slot definition.

As suggested by others, the reader errors are because `:,symbol is not valid Lisp. But it's easy enough to just pass a keyword into the macro in the first place.

Nathan Davis
Interesting. I was unaware that defclass itself was a macro. I think I will take Rainer's advice and get a paper Lisp book.
Paul Nathan
@Paul Nathan, all DEFSOMETHINGs are usually macros.
Rainer Joswig