如何在 LISP 中调用带有参数而不是列表的宏?

How to call a macro with a parameter instead of list in LISP?

根据 practical common lisp 参考中提供的示例,我定义了一个宏来创建 class,如下所示。

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

函数 slot->declass-slot 采用单个参数并生成描述 class 中槽的标准行。代码如下:

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

例如,

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

所有这些工作正常,当我创建一个 class 'model' 如下:

(define-class model (nom id))

但是假设我定义了一个参数。

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

然后,代码以错误结束:

The value *TEST* is not of type LIST.

怎么了?

您的 define-class 宏不计算其 class-slots 参数。 您可以 "fix" 您的代码如下:

(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*)

请注意,您现在必须引用 define-class 的第二个参数。

另请注意,您现在使用的是 eval(在本例中是有充分理由的)。

最后请注意,我严重怀疑您是否真的想这样做。很可能您不需要这种程度的活力,而且您只是无缘无故地使您的生活复杂化。

例如,如果您只想获取 class 个插槽的列表(使用您的 *test* 变量),您应该改用 MOP。 事实上你可以让你的宏扩展到 function ensure-class:

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

但这依赖于一个有点厚颜无耻的假设,即您的实施是 MOP-compliant

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

您不应该尝试这样做,原因与您从不尝试这样做的原因相同:

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

宏的目的是而不是评估参数,以便您可以对它们进行操作。如果您出于某种原因 do 需要宏版本和非宏版本,您 可以 做的是定义就函数而言的宏功能,然后在需要宏时将函数包装在宏中。例如,(对于不是特别健壮的)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))

那你想用宏版就用,用功能版就用。但是,就您而言,这不是一个完美的解决方案,因为您正在扩展到另一个宏调用。