1

I was working with common lisp, and found myself typing up slot definitions of the following form quite a lot:

(name :initarg :name :accessor name)

And so I thought to concoct a macro to speed up this. I came up with the following:

(defmacro quickslot (name)
`(,name :initarg ,(intern (string-upcase name) "KEYWORD") :accessor ,name))

A dirty hack, no doubt, but functional. Or so I thought. When I tried to run my code, I cam across a snag: since defclass is a macro, the arguments are passed to it unevaluated. That means, instead of seeing

(x :initarg :x :accessor x)

It sees

(quickslot x)

Which, of course, signals an error.

The answer, it seems to me, would be to somehow control the order of macro expansion in order to make sure quickslot is expanded before defclass. Which brings me to my question: how would one accomplish this? Or, if you have a different solution to my initial conundrum, that would not go unappreciated either.

  • 1
    A word of advice: Instead of "improving" the basic Common Lisp syntax through trivial macros, use the help of your editor to insert the code that you deem verbose now automatically. The syntax of defclass is a particular victim of beginner-written macros. – hans23 Dec 27 '12 at 10:19
  • Another possible option is to use `defstruct` instead of `defclass`. It is more lihtweight, in addition to defining initargs and accessors, it defines a readable printing method. – sds Jan 01 '13 at 23:02

3 Answers3

4

That's not really worthy of a macro. Macros typically take literal Lisp source code as their input.

Instead you could just use a function. From Practical Common Lisp, Ch.24:

(defun as-keyword (sym) (intern (string sym) :keyword))

(defun slot->defclass-slot (spec)
  (let ((name (first spec)))
    `(,name :initarg ,(as-keyword name) :accessor ,name)))

Then you could do something like (again adapted from PCL):

(defmacro my-defclass (name slots)
  `(defclass ,name ()
     ,(mapcar #'slot->defclass-slot slots)))
ChrisM
  • 2,128
  • 1
  • 23
  • 41
1

No, you can't do it. You could write a macro around defclass instead though (that has some special syntax for your quickslots).

Vsevolod Dyomkin
  • 9,343
  • 2
  • 31
  • 36
Cubic
  • 14,902
  • 5
  • 47
  • 92
1

You could approach the problem entirely differently and come up with a reader-macro that instructs the reader to call macroexpand on the code that follows it, that would be more generic then just the one purpose for slots declaration in the class. But the complete solution would be somewhat involved, because you'd had to account for many peculiarities and demands of the reader, however, even something as ugly as this would do the job:

(defmacro quickslot (name)
`(,name :initarg ,(intern (string-upcase name) "KEYWORD") :accessor ,name))

(macroexpand '(defclass test-class ()
               (#.(macroexpand '(quickslot some-slot)))))

So, what you'd have to do would be something like an alias to #.(macroexpand ...)


And... here you go:

(set-macro-character
 #\{
 #'(lambda (str char)
     (declare (ignore char))
     (let ((*readtable* (copy-readtable *readtable* nil))
           (reading-p t))
       (set-macro-character
        #\}
        #'(lambda (stream char)
            (declare (ignore char stream))
            (setf reading-p nil)))
       (loop for exp = (read str nil nil t)
          while reading-p
          collect (macroexpand exp)))))

(read-from-string "'(defclass test-class ()
               {(quickslot some-slot)
               (quickslot some-other-slot)})")
'(DEFCLASS TEST-CLASS NIL
           ((SOME-SLOT :INITARG :SOME-SLOT :ACCESSOR SOME-SLOT)
            (SOME-OTHER-SLOT :INITARG :SOME-OTHER-SLOT :ACCESSOR
             SOME-OTHER-SLOT)))

:)