0
votes

Based on the example provide in the practical common lisp reference, I define a macro to create a class as followed.

(defmacro define-class (class-name class-slot)
  `(defclass ,class-name ()
     ,(mapcar #'slot->defclass-slot class-slot)))) 

The function slot->declass-slot take a single argument and generate a standard line describing a slot in a class. The code is the following:

(defun slot->defclass-slot (spec)
  `(,spec :initarg ,(as-keyword spec) :accessor ,spec :initform 0))

For example,

(slot->defclass-slot 'nom)
(NOM :INITARG :NOM :ACCESSOR NOM :INITFORM 0)

All this work fine, when I create a class 'model' as follow:

(define-class model (nom id))

But suppose that I define a parameter instead.

(defparameter *test* '(nom id))
(define-class model *test*)

Then, the code end-up in an error:

The value *TEST* is not of type LIST.

What is wrong?

2
Just find some related discussion Basic-lisp-macro-questionXaving

2 Answers

4
votes

Your define-class macro does not evaluate its class-slots argument. You can "fix" your code like this:

(defmacro define-class (class-name class-slots)
  `(eval 
    `(defclass ,',class-name ()
       ,@(mapcar #'slot->defclass-slot ,class-slots))))
(macroexpand-1 '(define-class model '(nom id)))
(defparameter *test* '(nom id))
(define-class model *test*)

Note that you now have to quote the literal second argument to define-class.

Note also that you are now using eval (for a good reason, in this case).

Note finally that I seriously doubt that you truly want to do this. Chances are you don't need this level of dynamism, and you are just complicating your life for no good reason.

E.g., if you just want to get the list of class slots (using your *test* variable), you should use MOP instead. In fact you can make your macro expand to the function ensure-class:

> (mop:ensure-class 'foo :direct-slots '((:name a)))
#<STANDARD-CLASS FOO>

but this relies on a somewhat brazen assumption that your implementation is MOP-compliant.

1
votes
(defparameter *test* '(nom id))
(define-class model *test*)

You shouldn't try to do this, for the same reason that you never try to do:

(with-open-file '(...)
  ...)

The point of the macro is to not evaluate the arguments in order that you can do something with them. What you can do instead, if you do for some reason, need both a macro- version and non-macro- version, is to define the macro functionality in terms of a function, and then wrap the function in a macro when you need a macro. E.g., (for a not-particularly robust) with-open-file):

(defun %with-open-file (filename function &rest args)
  (let ((file (apply 'open filename args)))
    (prog1 (funcall function file)
      (close file))))

(defmacro with-open-file ((var filename &rest args) &body body)
  `(%with-open-file ,filename
     (lambda (,var) ,@body)
     ,@args))

Then you can use the macro-version when you want it, and the function-version when you want it. In your case, though, that's not a perfect solution, since you're expanding to another macro call.