从使用过的外部包访问 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 起作用的原因。但是不包括通用插槽访问器函数的符号。

  1. 考虑因素: 程序的体系结构不应计划为 share/export 对象和插槽访问。它设计得不好 [=7​​1=] slots/accessor-functions.

  2. 考虑因素: 您可以构建一个自定义函数,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 的符号和带有包名称前缀的访问函数。