【问题标题】:Access CLOS-object slots from used external package从使用的外部包访问 CLOS 对象槽
【发布时间】:2017-01-10 17:50:38
【问题描述】:

我正在学习构建我的 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

虽然类 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

要以编程方式访问我的插槽,在这种情况下,我需要将原始包名与插槽名合并,以从外部类获取/设置插槽...

我的理解

来自 CLOS 对象的访问函数是通用函数,属于包,它们已通过 DEFCLASS 定义,在本例中为:MY-PROJECT.A

由 MY-PROJECT.B 中的 (use-package :my-project.a) 导入导出的符号,这就是 DESCRIBE 起作用的原因。但不包括通用插槽访问器功能的符号。

  1. 注意事项: 不应计划程序的体系结构来共享/导出对象和插槽访问。它不适合批量导入/导出插槽/访问器功能。

  2. 注意事项: 您可以构建一个自定义函数,通过其包中的 slot-accessor-function 获取/设置插槽,因此只有一个接口函数可以导出?

我的问题:

这种处理外部 CLOS 对象的方式似乎不是可行的方法。 如何以合理的方式导出/导入这些访问器函数,而不需要手动列出每个插槽?

编辑/解决方案

我的术语和插槽与访问器函数的使用是导致此问题的原因(非常感谢@RainerJoswig 清理术语)。

我没有使用导出版本的 MY-SLOT-1-ACCESSOR 函数,它可以按预期工作,但如果我想访问每个插槽中的所有插槽,则需要我“批量导出”它们其他外包装。 @sds 很好地展示了如何做到这一点,并指出了我的方法的一般问题。非常感谢:)

在我看来,我希望只导出对象并获得对所有内部函数的完全访问权限。但这对 CLOS 来说是错误的方式,因为符号和方法不共享与类/对象的直接绑定,我必须适应更好的代码组织。

【问题讨论】:

  • 请在您的问题中清除插槽名称和访问器函数之间的区别。
  • 看来您只需要下定决心导出(或不导出)符号 my-slot-1。如果您认为应该将其导出,请执行此操作。如果您不想在打印对象中以调试方式使用它,请使用 :: 技巧引用它。如果 print-object 不应该访问未导出的符号,请创建一个访问器并将其导出(但我认为我们对包装变得愚蠢)。

标签: common-lisp packages clos


【解决方案1】:

术语

这个问题并没有明确插槽、插槽名称和插槽访问器函数之间的区别。将插槽名称和访问器函数混为一谈并不是一个好主意。你应该清楚什么是什么。

(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 并且没有名字 与其他符号冲突。

只需使用类和访问器函数的符号,加上它们的包名。

【讨论】:

  • 非常感谢。将访问器功能放在单独的包中让我大开眼界。虽然 Peter 的 PCL 中的这些插槽大多使用相同的名称(在大多数 PCL 示例中)作为插槽和访问器(以及 initarg),但存在很多混淆。
【解决方案2】:

导出所有访问器

您可以使用MOP 获取读者列表和 您的班级的作家,然后使用

像这样:

(dolist (slot (class-direct-slots (find-class 'your-class-name)))
  (dolist (reader (slot-definition-readers slot))
    (export reader)))

为什么这么复杂?

因为你确实想要那样做。

需要不加选择地访问一个类的所有槽的所有代码 应该与类在同一个包中。

您导出的唯一符号应该是您需要导出的符号,并且 他们应该经过你的明确审查。

【讨论】:

  • +1 但mapcan 可能会产生不希望的结果:此函数返回的列表不会被实现改变。如果可移植程序允许更改此列表,则结果未定义。 (slot-definition-readers)。另请参阅stackoverflow.com/q/38452350/124319
  • @coredump:感谢您的链接 - 我的信息量不够大;我现在修复了代码。
  • 原来的问题不是不允许批量导出吗?
  • 我不喜欢手动批量导入,但这是 解决方案,如何不手动进行。一般来说,我猜这是对命名空间的污染,并不是很有用。这就是为什么我也很感谢提示我想出更好的设计! @RainerJoswig 在这方面做得很好。我猜一开始很难估计附加的类名和导入的符号之间的权衡。
【解决方案3】:

您的 print-object-slot 函数正在尝试调用一个名为 slot-name 的函数,不是由变量 slot-name 命名的函数。你想在这里使用funcall

(defun print-object-slot (slot-name object)
  (format nil "slot-value: ~a" (funcall slot-name object)))

【讨论】:

  • 不,他想使用 SLOT-VALUE。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-18
  • 2021-06-09
  • 1970-01-01
  • 1970-01-01
  • 2020-05-01
相关资源
最近更新 更多