如何在 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))
那你想用宏版就用,用功能版就用。但是,就您而言,这不是一个完美的解决方案,因为您正在扩展到另一个宏调用。
根据 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))
那你想用宏版就用,用功能版就用。但是,就您而言,这不是一个完美的解决方案,因为您正在扩展到另一个宏调用。