0

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?

1

2 Answers 2

4

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.

Sign up to request clarification or add additional context in comments.

3 Comments

All the command but the last run properly. For the last, I get the following message In DEFCLASS MODEL, the slot name :INITARG is a keyword. To answer your question, I do all that because I don't know before I read a cvs file what will be the exact attributs of the class model. So, I have make things somehow generic not only for the class definition, but also for later display in a web page. I think the class definition is already compliant with the MOP.
without @ in the macro definition, it works better. Thank you.
Since the meta-object protocol was mentioned, you can define classes dynamically with ensure-class.
1
(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.

2 Comments

nice idea. I redefine the macro as a function, and edit a new macro as follow (defmacro %define-class (name slots) `(define-class ,name ,slots)). Then, the command: (%define-class 'model *test*) runs, but it return me the list: (DEFCLASS MODEL NIL (NOM :INITARG :NOM :ACCESSOR NOM :INITFORM 0) (IDD :INITARG :IDD :ACCESSOR IDD :INITFORM 0)). It's exactly what I expected, but not in list form. Somehow, I have the feeling that I never end at right level and that I should use extra eval command. I probably missunderstood something in MACRO. I'm not sure how to overcome this.
@Xaving As I said in the answer, "In your case, though, that's not a perfect solution, since you're expanding to another macro call." This approach won't really work here, since the final step that you need is a macro call, not a function call.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.