7
votes

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.


Six months later -

(defun build-var (classname var)
  (list var 
        :initform nil
        :accessor (intern (concatenate 'string (string classname) "-" 
                                       (string var)))
        :initarg (intern (string var) :keyword)))

(defun build-varlist (classname varlist)
   (loop for var in varlist 
         collect (build-var classname var)))


(defmacro defobject (name &rest varlist)
  "Defines a class with a set of behavior. 
   Variables are accessed by name-varname.

   (defobject classname v1 v2 v3)
  "
  `(defclass ,name ()
     ,(build-varlist name varlist))):

Two and a half years later.

I discovered the six-month-old code in the wild elsewhere. While I'm flattered, it also reminds me to update this.

If you like this idea, I keep this code gardened at: https://github.com/pnathan/defobject . As before, its goal is to produce CLOS classes with the minimum of repetitive typing. A similar system exists called DEFCLASS-STAR. Interested parties are advised to review both.

4

4 Answers

5
votes

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.

3
votes

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)

2
votes

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

0
votes

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.