You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

编写自动生成类槽访问器的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)))))

代码解释

  1. find-class ',class:直接通过类名找到对应的类对象,避免实例化类(如果类有必填初始化参数,实例化会报错)。
  2. loop ... collect:收集每个槽对应的defun表达式,而不是用do执行(collect会把每个表达式加入列表)。
  3. 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.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 11:23:43