我正在学习构建我的CL程序,现在在使用包编程时使用CLOS时遇到了麻烦.
(defpackage :my-project.a (:use :cl) (:export create-my-object my-object ; EXPORT SINGLE SLOTS? my-slot-1 ; my-slot-... ; my-slot-n ; OR EXPORT ALL ACCESSOR-FUNCTIONS? my-slot-1-accessor ; my-slot-n-accessor... )) (defpackage :my-project.b (:use :cl :my-project.a) (:export print-object-slot))
MY-OBJECT课程在MY-PROJECT.A中定义
(in-package :my-project.a) (defclass my-object () ((my-slot-1 :accessor my-slot-1-accessor :initarg :my-slot-1) ;... more slots ; (my-slot-2 :accessor my-slot-2-accessor :initarg :my-slot-2) ; (my-slot-n :accessor my-slot-n-accessor :initarg :my-slot-n) ))
作为对象的一些CREATOR函数
(defun create-my-object () (make-instance 'my-object :my-slot-1 "string" ;; further slots... ))
在MY-PROJECT.B包中有一些函数,例如PRINT-OBJECT,它应该处理从函数实例化的对象
(in-package :my-project.b) (defun print-object-slot (slot-name object) (format nil "slot-value: ~a" (SLOT-VALUE object slot-name)))
执行以下代码时不起作用
(in-package :my-project.b) (describe 'my-object) ; works (print-object-slot 'my-slot-1 ; while this works: 'my-project.a:my-slot-1 [if slot is exported] (create-my-object)) ;; ==> slot MY-PROJECT.B:MY-SLOT-1 is missing from the object ;; MY-PROJECT.A:MY-OBJECT
要以编程方式访问我的插槽,在这种情况下,我需要将原始package-name与slot-name合并,以从外部类获取/ setf插槽...
来自CLOS对象的访问器函数是属于包的通用函数,它们通过DEFCLASS定义,在这种情况下:MY-PROJECT.A
通过(use-package :my-project.a)
在MY-PROJECT.B,导出的符号都是进口的,这就是为什么DESCRIBE作品.但是不包括通用槽访问器功能的符号.
考虑:不应计划程序的体系结构来共享/导出对象和插槽访问.它不适合批量导入/导出插槽/访问器功能.
考虑:您可以构建一个自定义函数,通过其包中的slot-accessor-function获取/设置插槽,因此只有一个接口函数可以导出?
这种方式处理外部CLOS对象似乎不太可行.如何以理智的方式导出/导入这些访问者功能,而不是每个插槽手动列出?
我的终结和使用插槽与访问器功能是导致这个问题的原因(非常感谢@RainerJoswig清除术语).
我没有使用MY-SLOT-1-ACCESSOR功能的导出版本,它可以按预期工作,但如果我想访问所有其他外部包中的所有插槽,则需要我"批量导出"它们.@sds做了很好的工作来展示如何做到这一点,并指出了我的方法的一般问题.非常感谢 :)
在我看来,我希望只导出对象并获得对所有内部函数的完全访问权限.但这对于CLOS来说是错误的方式,因为符号和方法不共享对类/对象的直接绑定,我必须调整更好的代码组织.
术语
问题不会使插槽,插槽名称和插槽访问器功能之间的区别变得清晰.将插槽名称和访问器功能限制在一起并不是一个好主意.你应该清楚什么是什么.
(defpackage "GUI" (:use "CL") (:export ;; class window window-screen window-width window-height)) (defclass window () ((screen :accessor window-screen :initarg :screen) (width :accessor window-width :initarg :width :initform 640) (height :accessor window-height :initarg :height :initform 400)))
现在screen
是一个插槽名称,window-screen
是一个访问器功能.
插槽名称只是一个符号.您可以使用任何符号.例如,你也可以写(只是一个随机的例子,不要使用):
(defpackage "SLOTS" (:use)) (defpackage "AC" (:use) (:export "WINDOW-SCREEN" "WINDOW-WIDTH" "WINDOW-HEIGHT")) (defclass window () ((slots::screen :accessor ac:window-screen :initarg :screen) (slots::width :accessor ac:window-width :initarg :width :initform 640) (slots::height :accessor ac:window-height :initarg :height :initform 400)))
上面将使用包中的插槽名称slots
和包中的访问器ac
.
访问器是一种通用功能.
所以,当你写:
(defun foo (instance slot-name) ...)
我希望slot-name是一个符号,而不是一个访问器函数.
(defun foo (instance accessor) ...)
对于上面我会期望访问者是一个函数,而不是一个符号.
如果你真的希望明确区别,你可以编写方法:
(defmethod foo (instance (path symbol)) (slot-value instance path)) (defmethod foo (instance (path function)) (funcall function instance))
出口什么?
通常我会在包中导出访问者名称,但不会导出插槽名称.
进口?
但通常我甚至不会导入包:
(defpackage "GUI-GAME" (:use "CL"))
以上包不导入包gui
.它可以,但在这里它没有.
(defmethod describe-window ((w gui:window)) (format t "~% Window width:~a height:~a" (gui:window-width w) (gui:window-width h)))
优点是我在源代码中看到两件事:
gui:window
导出,因此是包接口的一部分
gui:window
实际上是来自包gui
,并没有名称与其他符号冲突.
只需使用类的符号和访问器函数及其包名前缀.
您可以使用MOP获取类的读者和作者列表,然后使用它们导出所有这些列表
find-class
class-direct-slots
slot-definition-readers
export
像这样:
(dolist (slot (class-direct-slots (find-class 'your-class-name))) (dolist (reader (slot-definition-readers slot)) (export reader)))为什么这么复杂?
因为你不希望这样做.
所有需要不加选择地访问类的所有插槽的代码应该与该类在同一个包中.
您导出的唯一符号应该是您需要导出的符号,并且应该由您明确审核.