编写自动生成类槽访问器的Lisp宏遇到的问题
问题分析与解决方案
你的问题出在宏的展开逻辑上——Common Lisp宏需要返回一个可求值的表达式,而你当前的loop使用do子句只是生成了defun表达式,但loop本身最终返回的是nil,所以整个宏展开后没有任何实际会被执行的代码,自然不会生成访问器函数。
核心问题拆解
当你去掉loop时,宏直接返回了单个defun的表达式,Lisp求值器会执行这个表达式定义函数;但加上loop后,do子句里的defun只是被生成但没有被收集返回,loop最后返回空值,宏展开结果是nil,相当于什么都没做。
修正后的宏代码
我们需要让loop收集所有生成的defun表达式,然后用progn包裹起来返回,这样求值器会依次执行每个defun来定义访问器。同时,还可以优化类槽的获取方式——不需要实例化类,直接用find-class获取类对象更高效:
(defmacro defacc (class) `(progn ,@(loop for slot in (class-slots (find-class ',class)) for slot-name = (slot-definition-name slot) collect `(defun ,slot-name (obj) (slot-value obj ',slot-name)))))
代码解释
find-class ',class:直接通过类名找到对应的类对象,避免实例化类(如果类有必填初始化参数,实例化会报错)。loop ... collect:收集每个槽对应的defun表达式,而不是用do执行(collect会把每个表达式加入列表)。progn包裹:将所有defun表达式放在progn里,这样宏展开后会依次执行这些defun,定义所有访问器函数。
测试示例
比如先定义一个类:
(defclass person () ((name :initarg :name) (age :initarg :age)))
然后调用宏:
(defacc person)
现在你就可以使用生成的访问器了:
(let ((p (make-instance 'person :name "Alice" :age 30))) (format t "Name: ~A, Age: ~A~%" (name p) (age p))) ;; 输出:Name: Alice, Age: 30
额外提示
如果想让访问器支持设置值(即生成setter函数),可以扩展宏,比如生成(setf name)这样的函数:
(defmacro defacc (class) `(progn ,@(loop for slot in (class-slots (find-class ',class)) for slot-name = (slot-definition-name slot) collect `(defun ,slot-name (obj) (slot-value obj ',slot-name)) collect `(defun (setf ,slot-name) (new-value obj) (setf (slot-value obj ',slot-name) new-value)))))
这样就可以用(setf (name p) "Bob")来修改槽值了。
内容的提问来源于stack exchange,提问作者Andrew S.
相关产品推荐
相关产品推荐

