Lisp中如何通过宏遍历类插槽?遇插槽缺失错误求解
问题:如何通过宏遍历类插槽并打印值?
先看正常工作的代码:定义了test-class类和test方法,用print-slot宏可以直接打印指定插槽的值:
(defclass test-class () ((data :initarg :data) (other :initarg :other))) (defmethod test ((tc test-class) &key) (macrolet ((print-slot (slot) `(with-slots (,slot) tc (format t "slot value: ~a~%" ,slot)))) (print-slot data) (print-slot other)))
调用后输出正常:
CL-USER> (test (make-instance 'test-class :data "data" :other "other")) slot value: data slot value: other
但改成用dolist遍历插槽列表调用宏时,会触发SB-PCL::MISSING-SLOT错误:
(defmethod test ((tc test-class) &key) (macrolet ((print-slot (slot) `(with-slots (,slot) tc (format t "slot value: ~a~%" ,slot)))) (dolist (slot '(data other)) (print-slot slot))))
错误信息:
CL-USER> (test (make-instance 'test-class :data "data" :other "other")) When attempting to read the slot's value (slot-value), the slot SLOT is missing from the object #<TEST-CLASS {1001E3FFB3}>. [Condition of type SB-PCL::MISSING-SLOT]
错误原因
宏是编译时展开的,不是运行时执行的。当你在dolist里写(print-slot slot),宏展开阶段会把slot当成一个插槽名符号,生成的代码是(with-slots (slot) tc ...)——也就是尝试访问对象里名为SLOT的插槽,而不是变量slot的值对应的插槽。你的类里根本没有叫SLOT的插槽,所以报错。
解决方法
方法1:用函数代替宏(推荐,适合运行时动态遍历)
把print-slot改成函数,用slot-value在运行时动态访问插槽,这样就能接收变量形式的插槽名:
(defclass test-class () ((data :initarg :data) (other :initarg :other))) (defmethod test ((tc test-class) &key) (flet ((print-slot (slot) (format t "slot value: ~a~%" (slot-value tc slot)))) (dolist (slot '(data other)) (print-slot slot))))
调用后就能正常遍历打印所有插槽的值。
方法2:用宏生成编译时的遍历代码(适合固定插槽列表)
如果一定要用宏实现,可以定义一个print-slots宏,让它在编译时就把插槽列表展开成多个print-slot调用,这样每个插槽名都是编译时确定的:
(defclass test-class () ((data :initarg :data) (other :initarg :other))) (defmethod test ((tc test-class) &key) (macrolet ((print-slot (slot) `(with-slots (,slot) tc (format t "slot value: ~a~%" ,slot))) (print-slots (&rest slots) `(progn ,@(loop for slot in slots collect `(print-slot ,slot))))) (print-slots data other)))
这个宏展开后和最初正常的代码完全一样,编译时就处理了每个插槽名,运行时直接访问对应插槽,不会有动态变量的问题。
内容的提问来源于stack exchange,提问作者John Graham
相关产品推荐
相关产品推荐

