Access CLOS-object slots from used external package
Asked Answered
D

3

5

I am learning to structure my CL programm and now having trouble to use the CLOS while programming in the large with packages.

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

While the class MY-OBJECT is defined in 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)
     ))

as some CREATOR function for the objects

(defun create-my-object ()
  (make-instance 'my-object
                 :my-slot-1 "string" 
                 ;; further slots...
                 ))

Having some function e.g. PRINT-OBJECT in the package MY-PROJECT.B, which should handle the object instanciated from a function

(in-package :my-project.b)

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

Problem

While executing following code doesn't work

(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

To access my slots programmatically, in this situation I would need to merge the originating package-name with the slot-name, to get/setf the slot from external classes...

My understanding

The accessor-functions from CLOS objects are generic functions, belonging to the package, where they have been defined via DEFCLASS, in this case: MY-PROJECT.A

By (use-package :my-project.a) in MY-PROJECT.B, the exported symbols are imported, that's why DESCRIBE works. But the symbols of the generic-slot-accessor-functions aren't included.

  1. Consideration: The architecture of the programm should NOT be planned to share/export objects and slot-access. It's not well designed to bulk-import/export slots/accessor-functions.

  2. Consideration: You can build a custom function, which get/sets the slots via the slot-accessor-function inside their package, so there is just one interface function to export?

My question:

This way handling external CLOS objects doesnt seem to be the way to go. How to export/import those accessor-functions in a sane way, without listing manually every single slot?

Edit/Solution

My terminolgy and use of slots vs. accessor-functions is a cause of this problem (thank you so much @RainerJoswig for clearing terminology up).

I did'nt use an exported version of MY-SLOT-1-ACCESSOR function, which would work as expected, but would need my to "bulk-export" them, if I would like to have access all slots in every other external package. @sds did a great job to show how to do this, and also at pointing out the general problem of my approach. Many thanks :)

In my mind, I wished to export just the object and gain full access to all the internal functions. But that's the wrong way for CLOS, since symbols and methods don't share direct bindings to the class/object, and I have to adapt better organisation of code.

Disinfectant answered 10/1, 2017 at 17:50 Comment(2)
Please clear up the difference between slot names and accessor functions in your question.Spicy
It seems you just need to make up your mind about exporting (or not) the symbol my-slot-1. If you think it should be exported, do so. If you do not but want to use it in a debugging way in print-object, reference it with the :: trick. If print-object is not supposed to access unexported symbols, create an accessor and export that (but then I think we are getting silly with packaging).Agape
H
5

Exporting all accessors

You can use MOP to get the list of readers and writers for your class and then export all of them, using

like this:

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

Why is it so complicated?

Because you do not want to do that.

All code which needs indiscriminate access to all slots of a class should be in the same package as the class.

The only symbols you export should be those you need to export, and they should be explicitly vetted by you.

Hippocras answered 10/1, 2017 at 18:53 Comment(4)
+1 but mapcan might give undesired results: the list returned by this function will not be mutated by the implementation. The results are undefined if a portable program allows this list to be mutated. (slot-definition-readers). See also https://mcmap.net/q/1008067/-is-there-a-way-to-gather-slot-definition-readers-from-all-the-inheritance-tree/124319.Ellita
@coredump: thanks for the link - mine was not as informative; I fixed the code now.Hippocras
Didn't the original question disallow bulk exporting?Agape
I disliked the bulk-import by hand, but this is the solution how to do it not manually. In general I guess it's a pollution of the namespace, and not really useful. That's why I am also thankful for the hint to think of a better design! Here did @RainerJoswig a great job to show the way. I guess it's quite hard in the beginning to estimate the tradeoff between appended classnames and imported symbols.Disinfectant
S
8

Terminology

The question does not make the differences between a slot, slot-name and a slot accessor function clear. Conflating slot names and accessor functions is not that a good idea. You should be clear what is what.

(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)))

Now screen is a slot name and window-screen is an accessor function.

The slot name is just a symbol. You can use any symbol for that. For example you can also write (just a random example, don't use):

(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)))

Above would use slot names in a package slots and accessors in a package ac.

An accessor is a generic function.

So, when you write:

(defun foo (instance slot-name)
  ...)

I would expect that slot-name is a symbol, not an accessor function.

(defun foo (instance accessor)
  ...)

For above I would expect accessor to be a function, not a symbol.

If you really want to make the difference clear, you can write methods:

(defmethod foo (instance (path symbol))
  (slot-value instance path))

(defmethod foo (instance (path function))
   (funcall function instance))

What to export?

Usually I would export accessor names in a package, but not slot names.

Import?

But often I would not even import the package:

(defpackage "GUI-GAME"
  (:use "CL"))

Above package does not import package gui. It could, but here it doesn't.

(defmethod describe-window ((w gui:window))
  (format t "~% Window width:~a height:~a"
          (gui:window-width w)
          (gui:window-width h)))

The advantage is that I see two things in the source code:

  • gui:window is exported and thus part of a package interface
  • gui:window is actually from the package gui and there is no name conflict with other symbols.

Just use the symbols for the class and the accessor functions with their package names prepended.

Spicy answered 10/1, 2017 at 22:15 Comment(1)
Thank you very much. Putting the accessor-functions in a separated packages was eye-opening for me. While those slots in Peter's PCL use mostly the same name (in most PCL examples) for slot and accessor (and initarg), there was much confusion.Disinfectant
H
5

Exporting all accessors

You can use MOP to get the list of readers and writers for your class and then export all of them, using

like this:

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

Why is it so complicated?

Because you do not want to do that.

All code which needs indiscriminate access to all slots of a class should be in the same package as the class.

The only symbols you export should be those you need to export, and they should be explicitly vetted by you.

Hippocras answered 10/1, 2017 at 18:53 Comment(4)
+1 but mapcan might give undesired results: the list returned by this function will not be mutated by the implementation. The results are undefined if a portable program allows this list to be mutated. (slot-definition-readers). See also https://mcmap.net/q/1008067/-is-there-a-way-to-gather-slot-definition-readers-from-all-the-inheritance-tree/124319.Ellita
@coredump: thanks for the link - mine was not as informative; I fixed the code now.Hippocras
Didn't the original question disallow bulk exporting?Agape
I disliked the bulk-import by hand, but this is the solution how to do it not manually. In general I guess it's a pollution of the namespace, and not really useful. That's why I am also thankful for the hint to think of a better design! Here did @RainerJoswig a great job to show the way. I guess it's quite hard in the beginning to estimate the tradeoff between appended classnames and imported symbols.Disinfectant
M
0

Your print-object-slot function is trying to call a function named literaly named slot-name, not the function named by the variable slot-name. You want to use funcall here.

(defun print-object-slot (slot-name object)
  (format nil "slot-value: ~a" (funcall slot-name object)))
Margarettamargarette answered 10/1, 2017 at 20:54 Comment(1)
No, he wants to use SLOT-VALUE.Spicy

© 2022 - 2024 — McMap. All rights reserved.