从使用过的外部包访问 CLOS 对象槽
Access CLOS-object slots from used external package
我正在学习构建我的 CL 程序,现在在使用大型软件包进行编程时无法使用 CLOS。
package.lisp
(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))
src.lisp
虽然 class 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...
))
有一些功能,例如PRINT-OBJECT 包中的 MY-PROJECT.B,
它应该处理从函数实例化的对象
(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
要以编程方式访问我的插槽,在这种情况下,我需要将原始包名称与插槽名称合并到 get/setf 来自外部 classes 的插槽...
我的理解
来自 CLOS 对象的访问器函数是通用函数,属于包,它们已通过 DEFCLASS 定义,在本例中为:MY-PROJECT.A
通过 (use-package :my-project.a)
in MY-PROJECT.B,导出的符号被导入,这就是 DESCRIBE 起作用的原因。但是不包括通用插槽访问器函数的符号。
考虑因素:
程序的体系结构不应计划为 share/export 对象和插槽访问。它设计得不好 [=71=] slots/accessor-functions.
考虑因素:
您可以构建一个自定义函数,get/sets 通过其包内的插槽访问器函数来创建插槽,因此只有一个接口函数可以导出?
我的问题:
这种处理外部 CLOS 对象的方式似乎不是可行的方法。
如何以理智的方式 export/import 这些访问器函数,而不是手动列出每个插槽?
Edit/Solution
我的术语和插槽与访问器函数的使用是导致此问题的原因(非常感谢@RainerJoswig 清理术语)。
我没有使用 MY-SLOT-1-ACCESSOR 函数的导出版本,它可以按预期工作,但如果我想访问所有插槽,则需要 "bulk-export" 它们在所有其他外部包装中。 @sds 很好地展示了如何做到这一点,并指出了我的方法的一般问题。非常感谢:)
在我看来,我希望只导出对象并获得对所有内部函数的完全访问权限。但这对 CLOS 来说是错误的方式,因为符号和方法不共享直接绑定到 class/object,我必须适应更好的代码组织。
正在导出所有访问器
您可以使用 MOP 获取读者列表和
class 的编写器,然后使用
导出所有这些
像这样:
(dolist (slot (class-direct-slots (find-class 'your-class-name)))
(dolist (reader (slot-definition-readers slot))
(export reader)))
为什么这么复杂?
因为你不想那样做。
所有需要不加区别地访问class所有插槽的代码
应该与 class.
在同一个包中
您导出的唯一符号应该是您需要导出的符号,并且
他们应该由您明确审查。
您的 print-object-slot
函数正在尝试调用一个名为 slot-name
的函数, 而不是 由变量 slot-name
命名的函数。你想在这里使用funcall
。
(defun print-object-slot (slot-name object)
(format nil "slot-value: ~a" (funcall slot-name object)))
术语
这个问题没有清楚说明插槽、插槽名称和插槽访问器函数之间的区别。将插槽名称和访问器函数混为一谈并不是一个好主意。你应该清楚什么是什么。
(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
并且没有名称
与其他符号冲突。
只需使用 class 的符号和带有包名称前缀的访问函数。
我正在学习构建我的 CL 程序,现在在使用大型软件包进行编程时无法使用 CLOS。
package.lisp
(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))
src.lisp
虽然 class 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...
))
有一些功能,例如PRINT-OBJECT 包中的 MY-PROJECT.B, 它应该处理从函数实例化的对象
(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
要以编程方式访问我的插槽,在这种情况下,我需要将原始包名称与插槽名称合并到 get/setf 来自外部 classes 的插槽...
我的理解
来自 CLOS 对象的访问器函数是通用函数,属于包,它们已通过 DEFCLASS 定义,在本例中为:MY-PROJECT.A
通过 (use-package :my-project.a)
in MY-PROJECT.B,导出的符号被导入,这就是 DESCRIBE 起作用的原因。但是不包括通用插槽访问器函数的符号。
考虑因素: 程序的体系结构不应计划为 share/export 对象和插槽访问。它设计得不好 [=71=] slots/accessor-functions.
考虑因素: 您可以构建一个自定义函数,get/sets 通过其包内的插槽访问器函数来创建插槽,因此只有一个接口函数可以导出?
我的问题:
这种处理外部 CLOS 对象的方式似乎不是可行的方法。 如何以理智的方式 export/import 这些访问器函数,而不是手动列出每个插槽?
Edit/Solution
我的术语和插槽与访问器函数的使用是导致此问题的原因(非常感谢@RainerJoswig 清理术语)。
我没有使用 MY-SLOT-1-ACCESSOR 函数的导出版本,它可以按预期工作,但如果我想访问所有插槽,则需要 "bulk-export" 它们在所有其他外部包装中。 @sds 很好地展示了如何做到这一点,并指出了我的方法的一般问题。非常感谢:)
在我看来,我希望只导出对象并获得对所有内部函数的完全访问权限。但这对 CLOS 来说是错误的方式,因为符号和方法不共享直接绑定到 class/object,我必须适应更好的代码组织。
正在导出所有访问器
您可以使用 MOP 获取读者列表和 class 的编写器,然后使用
导出所有这些像这样:
(dolist (slot (class-direct-slots (find-class 'your-class-name)))
(dolist (reader (slot-definition-readers slot))
(export reader)))
为什么这么复杂?
因为你不想那样做。
所有需要不加区别地访问class所有插槽的代码 应该与 class.
在同一个包中您导出的唯一符号应该是您需要导出的符号,并且 他们应该由您明确审查。
您的 print-object-slot
函数正在尝试调用一个名为 slot-name
的函数, 而不是 由变量 slot-name
命名的函数。你想在这里使用funcall
。
(defun print-object-slot (slot-name object)
(format nil "slot-value: ~a" (funcall slot-name object)))
术语
这个问题没有清楚说明插槽、插槽名称和插槽访问器函数之间的区别。将插槽名称和访问器函数混为一谈并不是一个好主意。你应该清楚什么是什么。
(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
并且没有名称 与其他符号冲突。
只需使用 class 的符号和带有包名称前缀的访问函数。